diff --git a/hibernate-core/src/main/java/org/hibernate/collection/spi/AbstractPersistentCollection.java b/hibernate-core/src/main/java/org/hibernate/collection/spi/AbstractPersistentCollection.java index ff2dd2b77884..fa6bf298b4e2 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/spi/AbstractPersistentCollection.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/spi/AbstractPersistentCollection.java @@ -1295,8 +1295,8 @@ protected static Collection getOrphans( // iterate over the *old* list for ( E old : oldElements ) { if ( !currentSaving.contains( old ) ) { - final Object oldId = ForeignKeys.getEntityIdentifierIfNotUnsaved( entityName, old, session ); - if ( !currentIds.contains( useIdDirect ? oldId : new TypedValue( idType, oldId ) ) ) { + final Object oldId = ForeignKeys.getEntityIdentifier( entityName, old, session ); + if ( oldId != null && !currentIds.contains( useIdDirect ? oldId : new TypedValue( idType, oldId ) ) ) { res.add( old ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/merge/BidirectionalOneToManyMergeTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/merge/BidirectionalOneToManyMergeTest.java new file mode 100644 index 000000000000..d9261f6be747 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/merge/BidirectionalOneToManyMergeTest.java @@ -0,0 +1,178 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.merge; + +import org.hibernate.testing.orm.junit.JiraKey; +import org.junit.Before; +import org.junit.Test; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import java.util.ArrayList; +import java.util.List; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Lisandro Fernandez (kelechul at gmail dot com) + */ +@JiraKey("HHH-13815") +public class BidirectionalOneToManyMergeTest extends org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ + Post.class, + PostComment.class, + }; + } + + @Before + public void setUp() { + doInJPA(this::entityManagerFactory, entityManager -> { + entityManager.persist( + new Post("High-Performance Java Persistence").setId(1L) + ); + }); + } + + @Test + public void testMerge() { + doInJPA(this::entityManagerFactory, entityManager -> { + Post post = entityManager.find(Post.class, 1L); + post.addComment(new PostComment("This post rocks!", post)); + post.getComments().isEmpty(); + entityManager.merge(post); + }); + } + + @Entity + public static class Post { + + @Id + private Long id; + + private String title; + + @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true) + private List comments = new ArrayList<>(); + + public Post() { + } + + public Post(String title) { + this.title = title; + } + + public Long getId() { + return id; + } + + public Post setId(Long id) { + this.id = id; + return this; + } + + public String getTitle() { + return title; + } + + public Post setTitle(String title) { + this.title = title; + return this; + } + + public List getComments() { + return comments; + } + + private Post setComments(List comments) { + this.comments = comments; + return this; + } + + public Post addComment(PostComment comment) { + comments.add(comment); + comment.setPost(this); + + return this; + } + + public Post removeComment(PostComment comment) { + comments.remove(comment); + comment.setPost(null); + + return this; + } + } + + @Entity + public static class PostComment { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String review; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_id") + private Post post; + + public PostComment() { + } + + public PostComment(String review, Post post) { + this.review = review; + this.post = post; + } + + public Long getId() { + return id; + } + + public PostComment setId(Long id) { + this.id = id; + return this; + } + + public String getReview() { + return review; + } + + public PostComment setReview(String review) { + this.review = review; + return this; + } + + public Post getPost() { + return post; + } + + public PostComment setPost(Post post) { + this.post = post; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PostComment)) return false; + return id != null && id.equals(((PostComment) o).getId()); + } + + @Override + public int hashCode() { + return 31; + } + } +}