I\'m using a relational DB using a single column pk with a few nested tables.
I need to add a simple archiving to my project. The archiving only happens when the appl
You could use mapstruct and create a method copy from object to object while ignoring id and every field that you don't want to copy.
If you have asscociations like one to many, many to many etc. you must have also a copy method in their mappers.
For binding children with parents, you can use afterMapping annotation and have add methods like Vlad Mihalcea has posted and do your work there or just do it in a service
You could clone the object and wipe the id and save it back down so a new Id is assigned.
so
Person person = EmployeeDAO.get(empId);
Person newPersonCopy = person.createCopyWithNoId()
EmployeeDAO.add(newPersonCopy);
in this case the createCopyWithNoId() would create a clone of the object and set the Id field to null. when you now add the new clone the hibernate engine will see it as a new object persist it and the database will assign a new primary key.
Please notice i avoided calling the method clone because what comes out is not an exact clone as we manipulate the Id (setting it null);
Another way to go would be to add a constructor that took in an object of type person and created a new object but simply didn't set the Id field leaving it at its default value of null. Again you persist using the DAO.
This is a very common question, so this answer is based on this article I wrote on my blog.
Using detach
or deep cloning as suggested by others are not the way to go when it comes to cloning an entity. If you try to make this process completely automatic, you are going to miss the point that not all attributes are worth duplicating.
Therefore, 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());
}
So, the copy constructor is the best way to address the entity clone/duplication problem.