From 856a00117034f6489df8fc90c7e02e3bb81f1bb0 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Fri, 26 Jul 2024 08:25:14 +0200 Subject: [PATCH] HHH-18195 migration guide --- .../BagDelayedOperationTest.java | 3 +- .../orm/test/map/MapIndexFormulaTest.java | 7 +- migration-guide.adoc | 105 +++++++++--------- 3 files changed, 60 insertions(+), 55 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/collection/delayedOperation/BagDelayedOperationTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/collection/delayedOperation/BagDelayedOperationTest.java index 2d32852cef..19b379bf82 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/collection/delayedOperation/BagDelayedOperationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/collection/delayedOperation/BagDelayedOperationTest.java @@ -105,8 +105,7 @@ public class BagDelayedOperationTest { Parent p = session.get( Parent.class, parentId ); assertFalse( Hibernate.isInitialized( p.getChildren() ) ); // add detached Child c - session.lock( c1, LockOptions.NONE ); - p.addChild( c1 ); + p.addChild( session.merge( c1 ) ); // collection should still be uninitialized assertFalse( Hibernate.isInitialized( p.getChildren() ) ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/map/MapIndexFormulaTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/map/MapIndexFormulaTest.java index e232e75bda..ad3e5e251b 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/map/MapIndexFormulaTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/map/MapIndexFormulaTest.java @@ -9,11 +9,12 @@ package org.hibernate.orm.test.map; import java.util.List; import java.util.Map; -import org.hibernate.LockMode; +import org.hibernate.dialect.MariaDBDialect; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.SkipForDialect; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -58,6 +59,7 @@ public class MapIndexFormulaTest { } @Test + @SkipForDialect( dialectClass = MariaDBDialect.class, reason = "HHH-18433") public void testIndexFormulaMap(SessionFactoryScope scope) { User turin = new User( "turin", "tiger" ); scope.inTransaction( @@ -90,8 +92,7 @@ public class MapIndexFormulaTest { assertEquals( 1, g.getUsers().size() ); Map smap = ( (User) g.getUsers().get( "gavin" ) ).getSession(); assertEquals( 1, smap.size() ); - session.lock( turin , LockMode.NONE); - User gavin = (User) g.getUsers().put( "gavin", turin ); + User gavin = (User) g.getUsers().put( "gavin", session.merge( turin ) ); session.remove( gavin ); assertEquals( 0l, diff --git a/migration-guide.adoc b/migration-guide.adoc index 5ab1283942..4b7bfa7610 100644 --- a/migration-guide.adoc +++ b/migration-guide.adoc @@ -153,56 +153,6 @@ String isDefault(); * Removed `org.hibernate.annotations.CascadeType.DELETE` in favor of `org.hibernate.annotations.CascadeType#REMOVE` * Removed the attribute value from `@DynamicInsert` and `@DynamicUpdate` -[WARNING] -=== -The removal of `CascadeType.SAVE_UPDATE` slightly changes the persist and flush behaviour (not affecting application using `Entitymanager`) that now conforms with the Jakarta JPA specifications. - -Persisting a transient entity with an associated detached entity where the association is annotated with cascade=all or cascade=persist throws an exception if the detached entity has not been re-associated with the the session using lock or merge. - -The same happens when flushing a managed entity having an associated detached entity. - -``` -@Entit -class Parent { - ... - - @OneToMany(cascade = CascadeType.ALL, mappedBy = "parent", orphanRemoval = true) - @LazyCollection(value = LazyCollectionOption.EXTRA) - private Set children = new HashSet<>(); -} - -@Entity -class Child { - - ... - - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - private Long id; - - @ManyToOne - private Parent parent; -} - - -``` - -``` -// Being Child c1 detached. - -scope.inTransaction( - session -> { - Parent parent = session.get( Parent.class, parentId ); - // add detached Child c - parent.addChild( c1 ); - } -); -``` -will throw an `jakarta.persistence.EntityExistsException` - -in order to fix the issue we can call `session.lock(c1,LockMode.NONE)` before adding `c1` to the `parent` or instead using `p.addChild( session.merge(c1) )`; - - [[ddl-implicit-datatype-timestamp]] == Default precision for timestamp on some databases @@ -244,6 +194,61 @@ one file at a time. This is now done across the entire set of `hbm.xml` files a While most users will never see this change, it might impact integrations which tie-in to XML processing. +[[flush-persist]] +== Session flush and persist + +The removal of `CascadeType.SAVE_UPDATE` slightly changes the persist and flush behaviour (not affecting application using `Entitymanager`) that now conforms with the Jakarta JPA specifications. + +Persisting a transient entity or flushing a manged entity with an associated detached entity having the association annotated with `cascade = CascadeType.ALL` or `cascade = CascadeType.PERSIST` throws now an `jakarta.persistence.EntityExistsException` if the detached entity has not been re-associated with the the Session. + +To re-associate the detached entity with the Session the `Session#merge` method can be used. + +Consider the following model + +``` +@Entity +class Parent { + + ... + + @OneToMany(cascade = CascadeType.ALL, mappedBy = "parent", orphanRemoval = true) + @LazyCollection(value = LazyCollectionOption.EXTRA) + private Set children = new HashSet<>(); + + public void addChild(Child child) { + children.add( child ); + child.setParent( this ); + } +} + +@Entity +class Child { + + ... + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @ManyToOne + private Parent parent; +} + +``` + +Assuming we have c1 as a detached Child, the following code will now result in jakarta.persistence.EntityExistsException being thrown at flush time: + +``` +Parent parent = session.get( Parent.class, parentId ); +parent.addChild( c1 ); +``` +Instead, c1 must first be re-associated with the Session using merge: + +``` +Parent parent = session.get( Parent.class, parentId ); +Child merged = session.merge( c1 ); +parent.addChild( merged ); +``` [[todo]] == Todos (dev)