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

前端 未结 9 616
抹茶落季
抹茶落季 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 20:53

    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

    0 讨论(0)
  • 2020-12-30 20:57

    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.

    0 讨论(0)
  • 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<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.

    0 讨论(0)
提交回复
热议问题