You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by bo...@apache.org on 2026/01/13 18:50:22 UTC
(grails-core) 10/13: Map 'save-update' cascade to 'persist,merge' for Hibernate 7 compatibility
This is an automated email from the ASF dual-hosted git repository.
borinquenkid pushed a commit to branch 8.0.x-hibernate7
in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit 2a650f1bcfb0938d45b879cc29208cf07bac8ee3
Author: Walter B Duque de Estrada <bo...@users.noreply.github.com>
AuthorDate: Sat Jan 10 22:23:57 2026 -0600
Map 'save-update' cascade to 'persist,merge' for Hibernate 7 compatibility
---
.../orm/hibernate/cfg/GrailsDomainBinder.java | 8 ++++++--
.../cfg/domainbinding/CascadeBehavior.java | 4 ++--
.../cfg/domainbinding/CascadeBehaviorFetcher.java | 8 ++++----
.../mapping/HibernateMappingBuilderTests.groovy | 10 +++++-----
.../domainbinding/CascadeBehaviorFetcherSpec.groovy | 19 ++++++++++++++-----
.../CascadeBehaviorPersisterSpec.groovy | 21 +++++++++++----------
6 files changed, 42 insertions(+), 28 deletions(-)
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
index 5ff60e052e..77c8e0443a 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java
@@ -128,7 +128,7 @@ public class GrailsDomainBinder
private static final String EMPTY_PATH = "";
public static final char UNDERSCORE = '_';
public static final String CASCADE_ALL = "all";
- public static final String CASCADE_SAVE_UPDATE = "save-update";
+ public static final String CASCADE_SAVE_UPDATE = "persist,merge";
public static final String CASCADE_NONE = "none";
public static final String BACKTICK = "`";
@@ -1176,11 +1176,15 @@ public class GrailsDomainBinder
for (String cascadeProp : cascades) {
String trimmedProp = cascadeProp.trim();
- if (CASCADE_SAVE_UPDATE.equals(trimmedProp) || CASCADE_ALL.equals(trimmedProp) || CASCADE_ALL_DELETE_ORPHAN.equals(trimmedProp)) {
+ if (CASCADE_SAVE_UPDATE.equals(trimmedProp) || CASCADE_ALL.equals(trimmedProp) || CASCADE_ALL_DELETE_ORPHAN.equals(trimmedProp) || "save-update".equals(trimmedProp)) {
return true;
}
}
+ if (cascade.contains("persist") && cascade.contains("merge")) {
+ return true;
+ }
+
return false;
}
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/CascadeBehavior.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/CascadeBehavior.java
index a055af348b..f383c143f3 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/CascadeBehavior.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/CascadeBehavior.java
@@ -14,9 +14,9 @@ public enum CascadeBehavior {
ALL("all"),
/**
- * Cascades save and update operations. Maps to "save-update".
+ * Cascades save and update operations. Maps to "persist,merge".
*/
- SAVE_UPDATE("save-update"),
+ SAVE_UPDATE("persist,merge"),
/**
* Cascades the merge operation. Maps to "merge".
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/CascadeBehaviorFetcher.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/CascadeBehaviorFetcher.java
index f61d9d0404..b48faaa982 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/CascadeBehaviorFetcher.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/CascadeBehaviorFetcher.java
@@ -54,12 +54,12 @@ public class CascadeBehaviorFetcher {
return ALL;
}
else if (association.isOneToOne()) {
- return ALL;
+ return association.isOwningSide() ? ALL : SAVE_UPDATE;
}
else if (association.isOneToMany()) {
- return ALL;
+ return association.isOwningSide() ? ALL : SAVE_UPDATE;
} else if (association.isManyToMany()) {
- return association.isCorrectlyOwned() || association.isCircular() ? ALL :NONE;
+ return association.isCorrectlyOwned() || association.isCircular() ? SAVE_UPDATE :NONE;
}
else if (association.isManyToOne()) {
if ( association.isCorrectlyOwned() && !association.isCircular()) {
@@ -76,7 +76,7 @@ public class CascadeBehaviorFetcher {
return ALL;
}
else if (Map.class.isAssignableFrom(association.getType())) {
- return ALL;
+ return association.isCorrectlyOwned() ? ALL :SAVE_UPDATE;
} else {
throw new MappingException("Unrecognized association type " + association.getType() );
}
diff --git a/grails-data-hibernate7/core/src/test/groovy/grails/gorm/hibernate/mapping/HibernateMappingBuilderTests.groovy b/grails-data-hibernate7/core/src/test/groovy/grails/gorm/hibernate/mapping/HibernateMappingBuilderTests.groovy
index 048bfaa6f9..b95889a1b1 100644
--- a/grails-data-hibernate7/core/src/test/groovy/grails/gorm/hibernate/mapping/HibernateMappingBuilderTests.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/grails/gorm/hibernate/mapping/HibernateMappingBuilderTests.groovy
@@ -278,7 +278,7 @@ class HibernateMappingBuilderTests {
def builder = new HibernateMappingBuilder("Foo")
def mapping = builder.evaluate {
columns {
- things cascade:'save-update'
+ things cascade:'persist,merge'
}
}
@@ -298,11 +298,11 @@ class HibernateMappingBuilderTests {
def builder = new HibernateMappingBuilder("Foo")
def mapping = builder.evaluate {
columns {
- things cascade:'save-update'
+ things cascade:'persist,merge'
}
}
- assertEquals 'save-update',mapping.getPropertyConfig('things').cascade
+ assertEquals 'persist,merge',mapping.getPropertyConfig('things').cascade
}
@Test
@@ -345,9 +345,9 @@ class HibernateMappingBuilderTests {
void testCascadesWithColumnsBlock() {
def builder = new HibernateMappingBuilder("Foo")
def mapping = builder.evaluate {
- things cascade:'save-update'
+ things cascade:'persist,merge'
}
- assertEquals 'save-update',mapping.getPropertyConfig('things').cascade
+ assertEquals 'persist,merge',mapping.getPropertyConfig('things').cascade
}
@Test
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CascadeBehaviorFetcherSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CascadeBehaviorFetcherSpec.groovy
index 11f865c48a..1b5d712cb6 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CascadeBehaviorFetcherSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CascadeBehaviorFetcherSpec.groovy
@@ -1,14 +1,23 @@
package org.grails.orm.hibernate.cfg.domainbinding
-import grails.gorm.annotation.Entity
-import grails.gorm.specs.HibernateGormDatastoreSpec
import jakarta.persistence.Embeddable
+
import org.hibernate.MappingException
import spock.lang.Shared
import spock.lang.Unroll
+import grails.gorm.annotation.Entity
+import grails.gorm.specs.HibernateGormDatastoreSpec
-import static org.grails.orm.hibernate.cfg.domainbinding.CascadeBehavior.*
+import static org.grails.orm.hibernate.cfg.domainbinding.CascadeBehavior.ALL
+import static org.grails.orm.hibernate.cfg.domainbinding.CascadeBehavior.DELETE
+import static org.grails.orm.hibernate.cfg.domainbinding.CascadeBehavior.EVICT
+import static org.grails.orm.hibernate.cfg.domainbinding.CascadeBehavior.LOCK
+import static org.grails.orm.hibernate.cfg.domainbinding.CascadeBehavior.MERGE
+import static org.grails.orm.hibernate.cfg.domainbinding.CascadeBehavior.NONE
+import static org.grails.orm.hibernate.cfg.domainbinding.CascadeBehavior.PERSIST
+import static org.grails.orm.hibernate.cfg.domainbinding.CascadeBehavior.REPLICATE
+import static org.grails.orm.hibernate.cfg.domainbinding.CascadeBehavior.SAVE_UPDATE
class CascadeBehaviorFetcherSpec extends HibernateGormDatastoreSpec {
@@ -18,7 +27,7 @@ class CascadeBehaviorFetcherSpec extends HibernateGormDatastoreSpec {
private static final List cascadeMetadataTestData = [
// --- UNIDIRECTIONAL hasMany (should be supported in Hibernate 6+) ---
["uni: explicit 'all'", AW_All_Uni, "books", BookUni, ALL.getValue()],
- ["uni: explicit 'save-update'", AW_SaveUpdate_Uni, "books", BookUni, SAVE_UPDATE.getValue()],
+ ["uni: explicit 'persist,merge'", AW_SaveUpdate_Uni, "books", BookUni, SAVE_UPDATE.getValue()],
["uni: explicit 'merge'", AW_Merge_Uni, "books", BookUni, MERGE.getValue()],
["uni: explicit 'delete'", AW_Delete_Uni, "books", BookUni, DELETE.getValue()],
["uni: explicit 'lock'", AW_Lock_Uni, "books", BookUni, LOCK.getValue()],
@@ -127,7 +136,7 @@ class CascadeBehaviorFetcherSpec extends HibernateGormDatastoreSpec {
@Entity class BookUni { String title }
@Entity class AW_All_Uni { static hasMany = [books: BookUni]; static mapping = { books cascade: 'all' } }
-@Entity class AW_SaveUpdate_Uni { static hasMany = [books: BookUni]; static mapping = { books cascade: 'save-update' } }
+@Entity class AW_SaveUpdate_Uni { static hasMany = [books: BookUni]; static mapping = { books cascade: 'persist,merge' } }
@Entity class AW_Merge_Uni { static hasMany = [books: BookUni]; static mapping = { books cascade: 'merge' } }
@Entity class AW_Delete_Uni { static hasMany = [books: BookUni]; static mapping = { books cascade: 'delete' } }
@Entity class AW_Lock_Uni { static hasMany = [books: BookUni]; static mapping = { books cascade: 'lock' } }
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CascadeBehaviorPersisterSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CascadeBehaviorPersisterSpec.groovy
index e73e2a57c2..4e3a312595 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CascadeBehaviorPersisterSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CascadeBehaviorPersisterSpec.groovy
@@ -1,14 +1,14 @@
package org.grails.orm.hibernate.cfg.domainbinding
-import grails.gorm.annotation.Entity
-import grails.gorm.transactions.Rollback
-import org.grails.orm.hibernate.HibernateDatastore
-import org.springframework.transaction.PlatformTransactionManager
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification
-import java.io.Serializable
+import org.springframework.transaction.PlatformTransactionManager
+
+import grails.gorm.annotation.Entity
+import grails.gorm.transactions.Rollback
+import org.grails.orm.hibernate.HibernateDatastore
/**
* Tests the persistence behavior of various one-to-many cascade settings in GORM.
@@ -81,7 +81,7 @@ class CascadeBehaviorPersisterSpec extends Specification {
}
@Rollback
- void "test unidirectional 'save-update' cascade persists child"() {
+ void "test unidirectional 'persist,merge' cascade persists child"() {
when: "A new owner is saved after adding a child"
def owner = new Owner_SaveUpdate_Uni_P(name: "Owner")
owner.addToChildren(new ChildPersister(title: "Child"))
@@ -132,7 +132,7 @@ class CascadeBehaviorPersisterSpec extends Specification {
}
@Rollback
- void "test bidirectional 'save-update' cascade persists child"() {
+ void "test bidirectional 'persist,merge' cascade persists child"() {
when: "A new owner is saved after adding a child"
def owner = new Owner_SaveUpdate_Bi_P(name: "Owner")
owner.addToChildren(new Child_BT_SaveUpdate_P(title: "Child"))
@@ -226,7 +226,7 @@ class CascadeBehaviorPersisterSpec extends Specification {
}
@Rollback
- void "test map without belongsTo cascade persists child"() {
+ void "test map without belongsTo 'persist,merge' cascade persists child"() {
when: "A new owner with a map entry is saved"
def owner = new MapParentP_SaveUpdate(name: "Owner")
owner.settings = [foo: new MapChildP_SaveUpdate(childValue: "bar")]
@@ -311,7 +311,7 @@ class Owner_All_Uni_P {
class Owner_SaveUpdate_Uni_P {
String name
static hasMany = [children: ChildPersister]
- static mapping = { children cascade: 'save-update' }
+ static mapping = { children cascade: 'persist,merge' }
}
@Entity
@@ -390,7 +390,7 @@ class Owner_SaveUpdate_Bi_P {
String name
Set<Child_BT_SaveUpdate_P> children
static hasMany = [children: Child_BT_SaveUpdate_P]
- static mapping = { children cascade: 'save-update' }
+ static mapping = { children cascade: 'persist,merge' }
}
@Entity
@@ -523,6 +523,7 @@ class MapParentP_SaveUpdate {
String name
static hasMany = [settings: MapChildP_SaveUpdate]
Map<String, MapChildP_SaveUpdate> settings
+ static mapping = { settings cascade: 'persist,merge' }
}
@Entity