diff --git a/.gitignore b/.gitignore index 253b7ce11..02471384b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ target *.iml .idea +.vscode # Eclipse .settings diff --git a/orm/hibernate-orm-7/src/test/java/org/hibernate/bugs/ORMUnitTestCase.java b/orm/hibernate-orm-7/src/test/java/org/hibernate/bugs/ORMUnitTestCase.java index 46f49f32b..6cf0eedbd 100644 --- a/orm/hibernate-orm-7/src/test/java/org/hibernate/bugs/ORMUnitTestCase.java +++ b/orm/hibernate-orm-7/src/test/java/org/hibernate/bugs/ORMUnitTestCase.java @@ -1,5 +1,9 @@ package org.hibernate.bugs; +import java.util.List; +import org.hibernate.bugs.entities.ChildEntity; +import org.hibernate.bugs.entities.AnotherEntity; +import org.hibernate.bugs.entities.ParentEntity; import org.hibernate.cfg.AvailableSettings; import org.hibernate.testing.orm.junit.DomainModel; @@ -7,49 +11,58 @@ import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -/** - * This template demonstrates how to develop a test case for Hibernate ORM, using its built-in unit test framework. - * Although ORMStandaloneTestCase is perfectly acceptable as a reproducer, usage of this class is much preferred. - * Since we nearly always include a regression test with bug fixes, providing your reproducer using this method - * simplifies the process. - *
- * What's even better? Fork hibernate-orm itself, add your test case directly to a module's unit tests, then
- * submit it as a PR!
- */
-@DomainModel(
- annotatedClasses = {
- // Add your entities here.
- // Foo.class,
- // Bar.class
- },
- // If you use *.hbm.xml mappings, instead of annotations, add the mappings here.
- xmlMappings = {
- // "org/hibernate/test/Foo.hbm.xml",
- // "org/hibernate/test/Bar.hbm.xml"
- }
-)
-@ServiceRegistry(
- // Add in any settings that are specific to your test. See resources/hibernate.properties for the defaults.
- settings = {
- // For your own convenience to see generated queries:
- @Setting(name = AvailableSettings.SHOW_SQL, value = "true"),
- @Setting(name = AvailableSettings.FORMAT_SQL, value = "true"),
- // @Setting( name = AvailableSettings.GENERATE_STATISTICS, value = "true" ),
-
- // Add your own settings that are a part of your quarkus configuration:
- // @Setting( name = AvailableSettings.SOME_CONFIGURATION_PROPERTY, value = "SOME_VALUE" ),
- }
-)
+@DomainModel(annotatedClasses = {
+ AnotherEntity.class,
+ ParentEntity.class,
+ ChildEntity.class,
+})
+@ServiceRegistry(settings = {
+ @Setting(name = AvailableSettings.SHOW_SQL, value = "true"),
+ @Setting(name = AvailableSettings.FORMAT_SQL, value = "true"),
+})
@SessionFactory
class ORMUnitTestCase {
- // Add your tests, using standard JUnit 5.
@Test
void hhh123Test(SessionFactoryScope scope) throws Exception {
- scope.inTransaction( session -> {
- // Do stuff...
- } );
+ // 1st tx : parent initialization with 2 children "a1" and "a2".
+ var parentId = scope.fromTransaction(session -> {
+ var parent = new ParentEntity();
+ parent.replaceChildren(List.of(new ChildEntity("a1"), new ChildEntity("a2")));
+ session.persist(parent);
+ return parent.getId();
+ });
+
+ scope.inTransaction(session -> {
+ for (var i = 0; i < 3; i++) {
+ // In the real-life scenario, we persist a new AnotherEntity before selecting a
+ // bunch of them, but it turns out
+ // it's not actually needed to reproduce.
+
+ // execute a HQL query that may trigger a "flush" but is not directly related to
+ // "ChildEntity".
+ session.createSelectionQuery("select p.id from AnotherEntity p", String.class).getResultList();
+
+ // attempt to remove current children and replace them with new ones.
+ var parent = session.find(ParentEntity.class, parentId);
+ parent.replaceChildren(List.of(new ChildEntity("b" + i), new ChildEntity("c" + i)));
+ }
+ });
+
+ scope.inTransaction(session -> {
+ var parent = session.createSelectionQuery("select p from ParentEntity p", ParentEntity.class)
+ .getSingleResult();
+ var allChildren = session.createSelectionQuery("select c from ChildEntity c", ChildEntity.class)
+ .getResultList();
+
+ // actual is 6 if annotated with @JoinColumn(updatable=false), 2 otherwise
+ Assertions.assertEquals(2, parent.getChildren().size());
+
+ // actual is always 6. Only "a1" and "a2" have actually been deleted.
+ Assertions.assertEquals(2, allChildren.size());
+ });
}
}
diff --git a/orm/hibernate-orm-7/src/test/java/org/hibernate/bugs/entities/AnotherEntity.java b/orm/hibernate-orm-7/src/test/java/org/hibernate/bugs/entities/AnotherEntity.java
new file mode 100644
index 000000000..fc3334556
--- /dev/null
+++ b/orm/hibernate-orm-7/src/test/java/org/hibernate/bugs/entities/AnotherEntity.java
@@ -0,0 +1,13 @@
+package org.hibernate.bugs.entities;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+
+@Entity
+public class AnotherEntity {
+ @Id
+ @GeneratedValue(strategy = GenerationType.UUID)
+ private String id;
+}
\ No newline at end of file
diff --git a/orm/hibernate-orm-7/src/test/java/org/hibernate/bugs/entities/ChildEntity.java b/orm/hibernate-orm-7/src/test/java/org/hibernate/bugs/entities/ChildEntity.java
new file mode 100644
index 000000000..e5ada4564
--- /dev/null
+++ b/orm/hibernate-orm-7/src/test/java/org/hibernate/bugs/entities/ChildEntity.java
@@ -0,0 +1,61 @@
+package org.hibernate.bugs.entities;
+
+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;
+
+@Entity
+public class ChildEntity {
+ @Id
+ @GeneratedValue(strategy = GenerationType.UUID)
+ private String id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(nullable = false, updatable = false)
+ private ParentEntity parent;
+
+ private String name;
+
+ public ChildEntity() {
+ }
+
+ public ChildEntity(String name) {
+ this.name = name;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setParent(ParentEntity parent) {
+ this.parent = parent;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (!(o instanceof ChildEntity))
+ return false;
+ return id != null && id.equals(((ChildEntity) o).getId());
+ }
+
+ @Override
+ public int hashCode() {
+ return getClass().hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "ChildEntity [id=" + id + ", name=" + name + "]";
+ }
+
+}
\ No newline at end of file
diff --git a/orm/hibernate-orm-7/src/test/java/org/hibernate/bugs/entities/ParentEntity.java b/orm/hibernate-orm-7/src/test/java/org/hibernate/bugs/entities/ParentEntity.java
new file mode 100644
index 000000000..04ce7ad96
--- /dev/null
+++ b/orm/hibernate-orm-7/src/test/java/org/hibernate/bugs/entities/ParentEntity.java
@@ -0,0 +1,52 @@
+package org.hibernate.bugs.entities;
+
+import java.util.ArrayList;
+import java.util.List;
+
+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.OneToMany;
+
+@Entity
+public class ParentEntity {
+ @Id
+ @GeneratedValue(strategy = GenerationType.UUID)
+ private String id;
+
+ @OneToMany(mappedBy = "parent", fetch = FetchType.LAZY, orphanRemoval = true, cascade = CascadeType.ALL)
+ private List