How to clone a JPA entity

JavaJpaOrmEntityClone

Java Problem Overview


I have a JPA entity already persisted in the database.
I would like to have a copy of it (with a different id), with some fields modified.

What is the easiest way to do this? Like:

  • setting it's @Id field to null and persisting it will work?
  • will I have to create a clone method for the entity (copying all fields except the @Id)?
  • is there any other approach (like using a cloning framework)?

Java Solutions


Solution 1 - Java

Use EntityManager.detach. It makes the bean no longer linked to the EntityManager. Then set the Id to the new Id (or null if automatic), change the fields that you need and persist.

Solution 2 - Java

When using EclipseLink, you can use the VERY handy CopyGroup-Feature:

http://wiki.eclipse.org/EclipseLink/Examples/JPA/AttributeGroup#CopyGroup

A big plus is that without much fiddling it properly clones private-owned relation-ships, too.

This is my code, cloning a Playlist with its private-owned @OneToMany-relationship is a matter of a few lines:

public Playlist cloneEntity( EntityManager em ) {
	CopyGroup group = new CopyGroup();
	group.setShouldResetPrimaryKey( true );
	Playlist copy = (Playlist)em.unwrap( JpaEntityManager.class ).copy( this, group );
	return copy;
}

Make sure that you use persist() to save this new object, merge() does not work.

Solution 3 - Java

You are better off using a copy constructor and controlling exactly what attributes need to be cloned.

So, if you have a Post entity like this one:

@Entity(name = "Post")
@Table(name = "post")
public class Post {
 
    @Id
    @GeneratedValue
    private Long id;
 
    private String title;
 
    @OneToMany(
        mappedBy = "post",
        cascade = CascadeType.ALL, 
        orphanRemoval = true
    )
    private List<PostComment> comments = new ArrayList<>();
 
    @OneToOne(
        mappedBy = "post",
        cascade = CascadeType.ALL, 
        orphanRemoval = true, 
        fetch = FetchType.LAZY
    )
    private PostDetails details;
 
    @ManyToMany
    @JoinTable(
        name = "post_tag",
        joinColumns = @JoinColumn(
            name = "post_id"
        ),
        inverseJoinColumns = @JoinColumn(
            name = "tag_id"
        )
    )
    private Set<Tag> tags = new HashSet<>();
 
    //Getters and setters omitted for brevity
 
    public void addComment(
            PostComment comment) {
        comments.add(comment);
        comment.setPost(this);
    }
 
    public void addDetails(
            PostDetails details) {
        this.details = details;
        details.setPost(this);
    }
 
    public void removeDetails() {
        this.details.setPost(null);
        this.details = null;
    }
}

It does not make sense to clone the comments when duplicating a Post and using it as a template for a new one:

Post post = entityManager.createQuery(
    "select p " +
    "from Post p " +
    "join fetch p.details " +
    "join fetch p.tags " +
    "where p.title = :title", Post.class)
.setParameter(
    "title", 
    "High-Performance Java Persistence, 1st edition"
)
.getSingleResult();
 
Post postClone = new Post(post);
postClone.setTitle(
    postClone.getTitle().replace("1st", "2nd")
);
entityManager.persist(postClone);

What you need to add to the Post entity is a copy constructor:

/**
 * Needed by Hibernate when hydrating the entity 
 * from the JDBC ResultSet
 */
private Post() {}
 
public Post(Post post) {
    this.title = post.title;
 
    addDetails(
        new PostDetails(post.details)
    );
 
    tags.addAll(post.getTags());
}

This is the best way to address the entity clone/duplication problem. Any other methods, which try to make this process completely automatic, miss the point that not all attributes are worth duplicating.

Solution 4 - Java

I face the same problem today : I have an entity in database and I want to :

  • get it from database
  • change one of its attributes value
  • create a clone of it
  • modify just some few attributes of the clone
  • persist clone in database
I succeed in doing following steps :

@PersistenceContext(unitName = "...")
private EntityManager entityManager;

public void findUpdateCloneAndModify(int myEntityId) {
  // retrieve entity from database
  MyEntity myEntity = entityManager.find(MyEntity.class, myEntityId);
  // modify the entity
  myEntity.setAnAttribute(newValue);
  // update modification in database
  myEntity = entityManager.merge(myEntity);
  // detach entity to use it as a new entity (clone)
  entityManager.detach(myEntity);
  myEntity.setId(0);
  // modify detached entity
  myEntity.setAnotherAttribute(otherValue);
  // persist modified clone in database
  myEntity = entityManager.merge(myEntity);
}

Remark : last step (clone persistence) does not work if I use 'persist' instead of 'merge', even if I note in debug mode that clone id has been changed after 'persist' command !

The problem I still face is that my first entity has not been modified before I detach it.

Solution 5 - Java

You could use mapping frameworks like Orika. http://orika-mapper.github.io/orika-docs/ Orika is a Java bean mapping framework that recursively copies data from one object to another. It is easy to configure and provides various flexibilities as well.

Here is how I have used it in my project:
added a dependecy :

 <dependency>
      <groupId>ma.glasnost.orika</groupId>
      <artifactId>orika-core</artifactId>
      <version>1.4.6</version>
</dependency>

Then use it in the code as follows:

MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
MapperFacade mapper=mapperFactory.getMapperFacade();
User mappedUser = mapper.map(oldUser, User.class);

This might help if you have many usecases where such kind of cloning is needed.

Solution 6 - Java

As mentioned in the comments to the accepted answer, detatch will ignore unflushed changes to the managed entity. If you are using spring you have another option which is to use org.springframework.beans.BeanUtils

Here you have BeanUtils.copyProperties(Object source, Object target). This will allow you to do a shallow copy without tampering with the entityManager.

Edit: quote from api doc: "this method is intended to perform a "shallow copy" of the properties and so complex properties (for example, nested ones) will not be copied."

This blog post may inform you more about deep copying java objects.

Solution 7 - Java

I just tried setting the id to null and it worked

address.setId(null);
address = addrRepo.save(address);

setting the id to null made it so it saved into a new record with new id since i have it automatically generated.

Solution 8 - Java

ModelMapper lib can be used for this purpose.

public MyEntity clone(MyEntity myInstance) {
    MyEntity newInstance = new MyEntity();
    new ModelMapper().map(myInstance, newInstance);
    return newInstance;
}

just add the maven dependency

<dependency>
    <groupId>org.modelmapper</groupId>
    <artifactId>modelmapper</artifactId>
    <version>2.3.2</version>
</dependency>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionkrisyView Question on Stackoverflow
Solution 1 - JavaSJuan76View Answer on Stackoverflow
Solution 2 - JavaschieferstapelView Answer on Stackoverflow
Solution 3 - JavaVlad MihalceaView Answer on Stackoverflow
Solution 4 - JavaBi30View Answer on Stackoverflow
Solution 5 - JavaAmritaView Answer on Stackoverflow
Solution 6 - JavaverthoView Answer on Stackoverflow
Solution 7 - JavaJanBrusView Answer on Stackoverflow
Solution 8 - JavaNicolas MafraView Answer on Stackoverflow