How to deep copy a Hibernate entity while using a newly generated entity identifier

前端 未结 9 612
抹茶落季
抹茶落季 2020-12-30 20:05

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

9条回答
  •  一整个雨季
    2020-12-30 21:00

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

提交回复
热议问题