How to clone a JPA entity
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.
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.
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.
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.