tags:

views:

878

answers:

4
+2  Q: 

Deep Copy in JPA

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!

A: 

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.

R. Kettelerij
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
+1  A: 

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
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
+1  A: 

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
This won't make a copy of fields marked with the transient keyword.
Sean McCauliff
@Sean: Thanks, a good point. (In our case these are vanilla JavaBeans.)
Jim Ferrans
+1  A: 

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