66

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)?
1
  • You have a copy constructor where you copy what field values you want. That is nothing at all to do with the JPA API. Basic Java.
    – user3973283
    Commented Sep 6, 2018 at 6:31

9 Answers 9

70

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.

6
  • I'm doing this with an existing entity, i.e. I'm basically changing the primary key (ID) of an entity object and merging it in order to have the desired object updated with other values. Any risks involved with this? It seems to work fine so far.
    – javaMS
    Commented Nov 19, 2012 at 7:47
  • 10
    One gotcha we encountered with this technique is that detach will ignore unflushed changes to the managed entity. e.g. If you want to (1) modify a managed entity, (2) detach the entity, and (3) persist a copy, then you must call flush before you detach, or else your modifications won't be persisted. From the Javadoc for flush: "Unflushed changes made to the entity if any (including removal of the entity), will not be synchronized to the database."
    – user201891
    Commented Mar 5, 2015 at 1:46
  • For those using Spring Data JPA (with its automatically generated repository implementations), see this: stackoverflow.com/a/26812963/56285
    – Jonik
    Commented Oct 28, 2015 at 10:26
  • 2
    If entity has table relationship and cascade like @OneToMany(cascade = CascadeType.ALL, mappedBy = "FIELD_NAME"), you probably need to loop each reference entity object and reset Id before persist, otherwise it may throw a PersistentObjectException: detached entity passed to persist.
    – atealxt
    Commented Mar 28, 2016 at 6:14
  • 2
    @javaMS two kinds of risks come to mind: what happens when entity being detached is referenced in collections on other entities in the session; does this work properly when using lazyloading/proxies. Didn't dig into it to find out.
    – doblak
    Commented Oct 27, 2016 at 9:41
20

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.

4
  • the code you've posted is implying you are working on a 2nd Edition of your book... (great!.. I have the 1st one)
    – johnm
    Commented Feb 21, 2019 at 14:16
  • I will surely publish a second edition. I'm waiting for Hibernate 6 to be launched and will release it in 2020. Commented Feb 21, 2019 at 15:09
  • Is that still a performant option to use if you have to duplicate 100+ entities (each having multiple relations)?
    – marco
    Commented Sep 16, 2021 at 6:37
  • The only way to answer that is to write a JMH test that measures the performance. Commented Sep 16, 2021 at 8:43
14

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.

0
4

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.

1
  • With EclipseLink 2.7.4 merge throws exception saying primary key cannot be updated. persist works fine. Commented Aug 2, 2020 at 7:21
4

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.

4

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.

3
  • This approach seemed to work best for my purposes. I just use BeanUtils.copyProperties(source, target, "id") and make a copy without its ID attribute, then persist it, where it is assigned an ID.
    – jwj
    Commented May 10, 2018 at 19:12
  • Will this work when there are one to many / many to one relations ?
    – nurettin
    Commented Oct 15, 2018 at 15:58
  • @nurettin, no. this is a shallow copy. 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." Try this for deep copy: javaworld.com/article/2077578/learn-java/…
    – vertho
    Commented Nov 8, 2018 at 9:31
0

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.

0

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>
0

You can use BeanUtils which is available from Spring or Apache Commons Lang. But you should not copy the @Id nor the @Version otherwise saving it will cause issues.

  public static <S, T> T copyEntityData(
    @NotNull S source,
    @NotNull T target,
    @NotNull EntityManager entityManager,
    String... ignoreProperties) {

    final var ignoredSet = new HashSet<>(Set.of(ignoreProperties));
    final var targetEntityType = entityManager.getMetamodel().entity(target.getClass());
    final var idName = targetEntityType.getId(targetEntityType.getIdType().getJavaType()).getName();
    ignoredSet.add(idName);
    Optional.ofNullable(targetEntityType.getVersion(Object.class))
      .map(Attribute::getName)
      .ifPresent(ignoredSet::add);

    BeanUtils.copyProperties(source, target, ignoredSet.toArray(String[]::new));
    return target;
  }

Note this does not detect if the field is updatable so non-updatable data should be ignored.

Not the answer you're looking for? Browse other questions tagged or ask your own question.