15

I would like to make a deep copy of an entity in JPA. I found an interesting discussion here: http://forums.java.net/jive/thread.jspa?messageID=253092&tstart=0

It sounded like the proposed solution was to set all @Id's to zero. Here's my basic code:


//Start a JPA session.
EntityManager em= emf.createEntityManager();
em.getTransaction().begin();

//Get the object I want to copy.
MyClass myObject=em.find(MyClass.class,id);

//Use reflection to find @Id's and set them to zero for all @OneToMany and @OneToOne relations.
//TODO:  write the ugly recursive code to do this.

//Hoping this will create a deep copy.
em.merge(myObject);

//Close the session.
em.getTransaction().commit();
em.close();

Is this a good strategy? Might anyone have this TODO code already written that they can share???

Thanks!

User1
  • 39,458
  • 69
  • 187
  • 265
  • The link is broken. Can you update it. – Kayser Sep 23 '12 at 14:20
  • Are you sure you want to do a deep copy? That could lead to having the whole database duplicated. I would rather stick with implementing the copying - tedious, but could save me a headache or worse, a server crashing in production. – Ondra Žižka Mar 16 '17 at 15:06

5 Answers5

5

I SOLVED THIS.

I created a component that makes this whole process for you based on the annotations of the package (javax.persistence).

Component already sets the id of the entity to null. He does all the analysis of the algorithm to be applied based on the type of each attribute relationship @OneToMany, @OneToOne or @ManyToMany.

Example

Person person = personDAO.find(1);
PersistenceCloner cloner = new PersistenceCloner(person); 
Person personCopy = cloner.generateCopyToPersist();

Download JAR and SOURCES: jpa-entitycloner

buræquete
  • 14,226
  • 4
  • 44
  • 89
3

I am not really sure if zeroing IDs of already managed objects is a good idea, esp. when your entities don't have equals() defined as equality of IDs. The JPA implementation might have had the managed objects in some cache and go beserk when playing with IDs of objects there.

I believe it would be safer to follow R.K.'s answer and do the real copying of objects.

Grzegorz Oledzki
  • 23,614
  • 16
  • 68
  • 106
  • Agreed. I have worked on a couple of projects where we tried to come up with a generalized way to make a deep copy of an object graph. The problem you always run into as the project grows is that you end up needing to copy different parts of the object graph for different use cases and/or different sets of properties within an object. Ultimately, it ends up being easier to just write the logic yourself rather than try to get clever with automatic deep cloning. Also, erasing IDs will almost certainly break some JPA implementations. – Rob H Jul 10 '09 at 17:48
2

If your objects implement Serializable, you can use writeObject() and readObject() to make a deep copy. We have a data transfer object hierarchy and support deep copies via this method in the abstract superclass (DTO):

/**
 * Reply a deep copy of this DTO.  This generic method works for any DTO subclass:
 * 
 *      Person person = new Person();
 *      Person copy = person.deepCopy();
 * 
 * Note: Using Java serialization is easy, but can be expensive.  Use with care.
 * 
 * @return A deep copy of this DTO.
 */
@SuppressWarnings("unchecked")
public <T extends DTO> T deepCopy()
{
    try
    {
        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;
        try
        {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            oos.writeObject(this);
            oos.flush();
            ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
            return (T) ois.readObject();
        }
        finally
        {
            oos.close();
            ois.close();
        }
    }
    catch ( ClassNotFoundException cnfe )
    {
        // Impossible, since both sides deal in the same loaded classes.
        return null;
    }
    catch ( IOException ioe )
    {
        // This has to be "impossible", given that oos and ois wrap a *byte array*.
        return null;
    }
}

(I'm certain that someone will find a reason why these exceptions can occur.)

Other serialization libraries (eg, XStream) could be used in the same manner.

Jim Ferrans
  • 30,582
  • 12
  • 56
  • 83
2

I was able to get a deep copy to work as described in the question. It is necessary to eagerly load the whole graph and reset the @Id's to null or zero. I found that the Hibernate SessionFactory actually has methods to help with this process.

The other recommendations above for deep copies didn't seem to work. Granted, the problem may have been between keyboard and chair. But it's working now.

Thanks everyone!

User1
  • 39,458
  • 69
  • 187
  • 265
  • 5
    Basically you said the we need to eagerly load the whole graph, but not often our graph has collections lazy initialized. You also commented that the SessionFactory has methods to help with the process to reset the @id's. Can you provide some code example, because i am facing the same problem and it's driving me crazy :) – Julio Helden Dec 01 '11 at 05:09
  • 1
    But you did not provide a piece of code. Which Hibernate SessionFactory method makes it – user725455 Jun 22 '20 at 10:06
1

Why would you want to do this? It sounds a bit like hacking.

That said Apache Commons BeanUtils contains cloneBean() and copyProperties() methods to make (shallow) object copies. To make a deep copy you could do write a method as proposed here.

Richard Kettelerij
  • 2,049
  • 14
  • 17
  • I want to make deep copies of data stored in the database that will be completely independent from the object it was copied from. For example: -Object1 is a deep copy of Object2. -Object1 has a child (from @OneToMany) that changes. -Object2's child is not supposed to change. – User1 Jul 09 '09 at 22:10
  • 2
    For deepCopy you can use SerializationUtils.clone() from apache commons – rozky Aug 14 '12 at 08:32