This is an automated email from the ASF dual-hosted git repository.

borinquenkid pushed a commit to branch 8.0.x-hibernate7-dev
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 714aba0c00b307f4d1ad8aebca9aa2ea1ac994a6
Author: Walter Duque de Estrada <[email protected]>
AuthorDate: Thu Mar 12 14:54:39 2026 -0500

    hibernate 7:  Consolidated Identity logic
---
 .../org/grails/orm/hibernate/cfg/Identity.groovy   |  5 ++
 .../domainbinding/binder/CompositeIdBinder.java    | 15 +++--
 .../cfg/domainbinding/binder/IdentityBinder.java   |  9 +--
 .../hibernate/GrailsHibernatePersistentEntity.java | 26 +++++++-
 .../domainbinding/hibernate/HibernateIdentity.java |  5 ++
 .../orm/hibernate/cfg/CompositeIdentitySpec.groovy |  9 +++
 .../cfg/GrailsHibernatePersistentEntitySpec.groovy | 78 ++++++++++++++++++++++
 .../grails/orm/hibernate/cfg/IdentitySpec.groovy   |  6 ++
 .../cfg/domainbinding/IdentityBinderSpec.groovy    | 35 +++++++---
 .../cfg/domainbinding/SimpleIdBinderSpec.groovy    | 33 +++++++++
 10 files changed, 201 insertions(+), 20 deletions(-)

diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/Identity.groovy
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/Identity.groovy
index c4607aef98..d1a8037e04 100644
--- 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/Identity.groovy
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/Identity.groovy
@@ -61,6 +61,11 @@ class Identity extends Property implements HibernateIdentity 
{
      */
     Map params = [:]
 
+    @Override
+    String[] getPropertyNames() {
+        name ? [name] as String[] : [] as String[]
+    }
+
     String determineGeneratorName(boolean useSequence) {
         if (generator != null && 
!(GrailsSequenceGeneratorEnum.NATIVE.toString() == generator && useSequence)) {
             return generator
diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/CompositeIdBinder.java
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/CompositeIdBinder.java
index 07828f97b2..90caf771c4 100644
--- 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/CompositeIdBinder.java
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/CompositeIdBinder.java
@@ -20,6 +20,7 @@ package org.grails.orm.hibernate.cfg.domainbinding.binder;
 
 import jakarta.annotation.Nonnull;
 
+import org.hibernate.MappingException;
 import org.hibernate.boot.spi.MetadataBuildingContext;
 import org.hibernate.mapping.Component;
 import org.hibernate.mapping.RootClass;
@@ -46,7 +47,7 @@ public class CompositeIdBinder {
     }
 
     public void bindCompositeId(
-            @Nonnull GrailsHibernatePersistentEntity domainClass, RootClass 
root, CompositeIdentity compositeIdentity) {
+            @Nonnull GrailsHibernatePersistentEntity domainClass, RootClass 
root,@Nonnull CompositeIdentity compositeIdentity) {
         Component id = new Component(metadataBuildingContext, root);
         id.setNullValue("undefined");
         root.setIdentifier(id);
@@ -60,10 +61,16 @@ public class CompositeIdBinder {
 
         id.setRoleName(path);
 
-        if (compositeIdentity == null) {
-            compositeIdentity = new CompositeIdentity();
+        HibernatePersistentProperty[] composite;
+        if (compositeIdentity != null) {
+            composite = compositeIdentity.getHibernateProperties(domainClass);
+        } else {
+            composite = domainClass.getCompositeIdentity();
+        }
+
+        if (composite == null || composite.length == 0) {
+            throw new MappingException("No composite identifier properties 
found for class [" + domainClass.getName() + "]");
         }
-        HibernatePersistentProperty[] composite = 
compositeIdentity.getHibernateProperties(domainClass);
 
         HibernatePersistentProperty identifierProp = domainClass.getIdentity();
         for (HibernatePersistentProperty property : composite) {
diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/IdentityBinder.java
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/IdentityBinder.java
index 0aadee5ad2..4d82139eda 100644
--- 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/IdentityBinder.java
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/IdentityBinder.java
@@ -39,16 +39,11 @@ public class IdentityBinder {
     }
 
     public void bindIdentity(@Nonnull GrailsHibernatePersistentEntity 
domainClass, RootClass root) {
-
         var id = domainClass.getHibernateIdentity();
-        if (id instanceof CompositeIdentity || (id == null && 
domainClass.getCompositeIdentity() != null)) {
+        if (id instanceof CompositeIdentity) {
             compositeIdBinder.bindCompositeId(domainClass, root, 
(CompositeIdentity) id);
         } else {
-            Identity identity = id instanceof Identity ? (Identity) id : null;
-            if (identity != null && identity.getName() == null) {
-                identity.setName(root.getEntityName());
-            }
-            simpleIdBinder.bindSimpleId(domainClass, root, identity, 
root.getTable());
+            simpleIdBinder.bindSimpleId(domainClass, root, (Identity) id, 
root.getTable());
         }
     }
 }
diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/GrailsHibernatePersistentEntity.java
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/GrailsHibernatePersistentEntity.java
index 6f4f96bb8f..b93bab2c98 100644
--- 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/GrailsHibernatePersistentEntity.java
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/GrailsHibernatePersistentEntity.java
@@ -35,6 +35,7 @@ import org.grails.datastore.mapping.model.PersistentEntity;
 import org.grails.datastore.mapping.model.PersistentProperty;
 import org.grails.datastore.mapping.model.config.GormProperties;
 import org.grails.orm.hibernate.cfg.CompositeIdentity;
+import org.grails.orm.hibernate.cfg.Identity;
 import org.grails.orm.hibernate.cfg.DiscriminatorConfig;
 import org.grails.orm.hibernate.cfg.Mapping;
 import org.grails.orm.hibernate.cfg.PersistentEntityNamingStrategy;
@@ -108,7 +109,30 @@ public interface GrailsHibernatePersistentEntity extends 
PersistentEntity {
     }
 
     default HibernateIdentity getHibernateIdentity() {
-        return 
Optional.ofNullable(getMappedForm()).map(Mapping::getIdentity).orElse(null);
+        return Optional.ofNullable(getMappedForm())
+                .map(Mapping::getIdentity)
+                .or(this::resolveCompositeIdentity)
+                .orElseGet(this::getDefaultIdentity);
+    }
+
+    private Optional<HibernateIdentity> resolveCompositeIdentity() {
+        return Optional.ofNullable(getCompositeIdentity())
+                .filter(compositeId -> compositeId.length > 1)
+                .map(compositeId -> {
+                    CompositeIdentity ci = new CompositeIdentity();
+                    ci.setPropertyNames(java.util.Arrays.stream(compositeId)
+                            .map(PersistentProperty::getName)
+                            .toArray(String[]::new));
+                    return ci;
+                });
+    }
+
+    private @Nonnull Identity getDefaultIdentity() {
+        Identity identity = new Identity();
+        identity.setName(Optional.ofNullable(getIdentity())
+                .map(PersistentProperty::getName)
+                .orElseGet(this::getName));
+        return identity;
     }
 
     @Override
diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateIdentity.java
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateIdentity.java
index 8fd44edc2d..1e87e5353a 100644
--- 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateIdentity.java
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateIdentity.java
@@ -33,4 +33,9 @@ public interface HibernateIdentity {
      * @param natural The natural id definition
      */
     void setNatural(NaturalId natural);
+
+    /**
+     * @return The property names that make up the identity
+     */
+    String[] getPropertyNames();
 }
diff --git 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/CompositeIdentitySpec.groovy
 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/CompositeIdentitySpec.groovy
index c7df24a6b0..eb56538041 100644
--- 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/CompositeIdentitySpec.groovy
+++ 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/CompositeIdentitySpec.groovy
@@ -66,4 +66,13 @@ class CompositeIdentitySpec extends Specification {
         1 * domainClass.getHibernatePropertyByName("invalid") >> null
         thrown(MappingException)
     }
+
+    def "test getPropertyNames"() {
+        given:
+        def propertyNames = ['prop1', 'prop2'] as String[]
+        def compositeIdentity = new CompositeIdentity(propertyNames: 
propertyNames)
+
+        expect:
+        compositeIdentity.getPropertyNames() == propertyNames
+    }
 }
diff --git 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/GrailsHibernatePersistentEntitySpec.groovy
 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/GrailsHibernatePersistentEntitySpec.groovy
index 5dc27afa53..353aafd6ff 100644
--- 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/GrailsHibernatePersistentEntitySpec.groovy
+++ 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/GrailsHibernatePersistentEntitySpec.groovy
@@ -235,6 +235,84 @@ class GrailsHibernatePersistentEntitySpec extends 
HibernateGormDatastoreSpec {
         result2 == ["'VEHICLE'", "'TRUCK'"] as Set
     }
 
+    def "test getHibernateIdentity returns mapping identity if available"() {
+        given:
+        def context = getMappingContext()
+        GrailsHibernatePersistentEntity entity = 
Spy(HibernatePersistentEntity, constructorArgs: [Person, context])
+        def mapping = Mock(Mapping)
+        def mappedIdentity = new Identity(name: "customId")
+
+        entity.getMappedForm() >> mapping
+        mapping.getIdentity() >> mappedIdentity
+
+        when:
+        def result = entity.getHibernateIdentity()
+
+        then:
+        result == mappedIdentity
+        ((Identity)result).name == "customId"
+    }
+
+    def "test getHibernateIdentity returns CompositeIdentity if entity has 
multiple ID properties"() {
+        given:
+        def context = getMappingContext()
+        GrailsHibernatePersistentEntity entity = 
Spy(HibernatePersistentEntity, constructorArgs: [Person, context])
+        def id1 = Mock(HibernatePersistentProperty)
+        def id2 = Mock(HibernatePersistentProperty)
+        id1.getName() >> "id1"
+        id2.getName() >> "id2"
+
+        entity.getMappedForm() >> null
+        entity.getCompositeIdentity() >> ([id1, id2] as 
HibernatePersistentProperty[])
+
+        when:
+        def result = entity.getHibernateIdentity()
+
+        then:
+        result instanceof CompositeIdentity
+        ((CompositeIdentity)result).propertyNames == ["id1", "id2"] as String[]
+    }
+
+    def "test getHibernateIdentity returns synthetic Identity if no mapping or 
composite ID"() {
+        given:
+        def context = getMappingContext()
+        GrailsHibernatePersistentEntity entity = 
Spy(HibernatePersistentEntity, constructorArgs: [Person, context])
+        def idProp = Mock(HibernatePersistentProperty)
+        idProp.getName() >> "myId"
+
+        entity.getMappedForm() >> null
+        entity.getCompositeIdentity() >> null
+        entity.getIdentity() >> idProp
+        entity.getName() >> "Person"
+
+        when:
+        def result = entity.getHibernateIdentity()
+
+        then:
+        result instanceof Identity
+        ((Identity)result).name == "myId"
+    }
+
+    def "test getHibernateIdentity defaults to entity name if identity name is 
null"() {
+        given:
+        def context = getMappingContext()
+        GrailsHibernatePersistentEntity entity = 
Spy(HibernatePersistentEntity, constructorArgs: [Person, context])
+        def idProp = Mock(HibernatePersistentProperty)
+        idProp.getName() >> null
+
+        entity.getMappedForm() >> null
+        entity.getCompositeIdentity() >> null
+        entity.getIdentity() >> idProp
+        entity.getName() >> "Person"
+
+        when:
+        def result = entity.getHibernateIdentity()
+
+        then:
+        result instanceof Identity
+        ((Identity)result).name == "Person"
+    }
+
     def "test getHibernateCompositeIdentity returns CompositeIdentity when 
conditions met"() {
         given:
         def context = getMappingContext()
diff --git 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/IdentitySpec.groovy
 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/IdentitySpec.groovy
index 3cc1965b72..7561328776 100644
--- 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/IdentitySpec.groovy
+++ 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/IdentitySpec.groovy
@@ -42,4 +42,10 @@ class IdentitySpec extends Specification {
         false           | null          | false       | 'native'
         false           | null          | true        | 'sequence-identity'
     }
+
+    def "test getPropertyNames"() {
+        expect:
+        new Identity(name: "id").getPropertyNames() == ["id"] as String[]
+        new Identity(name: null).getPropertyNames() == [] as String[]
+    }
 }
diff --git 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/IdentityBinderSpec.groovy
 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/IdentityBinderSpec.groovy
index 5650e0b8a2..de2bef34de 100644
--- 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/IdentityBinderSpec.groovy
+++ 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/IdentityBinderSpec.groovy
@@ -1,6 +1,23 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
 package org.grails.orm.hibernate.cfg.domainbinding
 
-
 import org.grails.datastore.mapping.model.PersistentProperty
 import org.grails.datastore.mapping.model.ClassMapping
 import 
org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernatePersistentProperty
@@ -35,6 +52,8 @@ class IdentityBinderSpec extends HibernateGormDatastoreSpec {
         def root = new 
RootClass(getGrailsDomainBinder().getMetadataBuildingContext())
         def mappings = Mock(InFlightMetadataCollector)
         def identifierProp = Mock(HibernatePersistentProperty)
+        def identity = new Identity()
+        domainClass.getHibernateIdentity() >> identity
         domainClass.getIdentity() >> identifierProp
         domainClass.getCompositeIdentity() >> null
 
@@ -43,7 +62,7 @@ class IdentityBinderSpec extends HibernateGormDatastoreSpec {
 
 
         then:
-        1 * simpleIdBinder.bindSimpleId(domainClass, root, null, _)
+        1 * simpleIdBinder.bindSimpleId(domainClass, root, identity, _)
     }
 
     def "should delegate to compositeIdBinder when mapping is null and 
domainClass has composite identity"() {
@@ -51,15 +70,17 @@ class IdentityBinderSpec extends HibernateGormDatastoreSpec 
{
         def domainClass = Mock(GrailsHibernatePersistentEntity)
         def root = new 
RootClass(getGrailsDomainBinder().getMetadataBuildingContext())
         def mappings = Mock(InFlightMetadataCollector)
-        def compositeProps = [Mock(HibernatePersistentProperty)] as 
HibernatePersistentProperty[]
+        def compositeProps = [Mock(HibernatePersistentProperty), 
Mock(HibernatePersistentProperty)] as HibernatePersistentProperty[]
+        def compositeIdentity = new CompositeIdentity()
         domainClass.getCompositeIdentity() >> compositeProps
+        domainClass.getHibernateIdentity() >> compositeIdentity
 
         when:
         binder.bindIdentity(domainClass, root)
 
 
         then:
-        1 * compositeIdBinder.bindCompositeId(domainClass, root, null)
+        1 * compositeIdBinder.bindCompositeId(domainClass, root, 
compositeIdentity)
     }
 
     def "should delegate to compositeIdBinder when mapping specifies composite 
identity"() {
@@ -120,14 +141,13 @@ class IdentityBinderSpec extends 
HibernateGormDatastoreSpec {
         1 * simpleIdBinder.bindSimpleId(domainClass, root, identity, _)
     }
 
-    def "should set entity name on identity if it is null"() {
+    def "should pass identity with name set to simpleIdBinder"() {
         given:
         def domainClass = Mock(GrailsHibernatePersistentEntity)
         def root = new 
RootClass(getGrailsDomainBinder().getMetadataBuildingContext())
-        root.setEntityName("MyEntity")
         def mappings = Mock(InFlightMetadataCollector)
         def gormMapping = Mock(Mapping)
-        def identity = new Identity()
+        def identity = new Identity(name: "MyEntity")
         domainClass.getHibernateIdentity() >> identity
         def identifierProp = Mock(HibernatePersistentProperty)
         domainClass.getIdentity() >> identifierProp
@@ -138,7 +158,6 @@ class IdentityBinderSpec extends HibernateGormDatastoreSpec 
{
 
 
         then:
-        identity.getName() == "MyEntity"
         1 * simpleIdBinder.bindSimpleId(domainClass, root, identity, _)
     }
 
diff --git 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/SimpleIdBinderSpec.groovy
 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/SimpleIdBinderSpec.groovy
index c0a20a4067..2c7aa30dff 100644
--- 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/SimpleIdBinderSpec.groovy
+++ 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/SimpleIdBinderSpec.groovy
@@ -10,12 +10,14 @@ import org.hibernate.mapping.BasicValue
 import org.hibernate.mapping.PrimaryKey
 import org.hibernate.mapping.RootClass
 import org.hibernate.mapping.Table
+import org.hibernate.mapping.Value
 
 import org.grails.orm.hibernate.cfg.domainbinding.binder.PropertyBinder
 import org.grails.orm.hibernate.cfg.domainbinding.binder.SimpleIdBinder
 import org.grails.orm.hibernate.cfg.domainbinding.binder.SimpleValueBinder
 import 
org.grails.orm.hibernate.cfg.domainbinding.generator.GrailsSequenceGeneratorEnum
 import org.grails.orm.hibernate.cfg.domainbinding.util.BasicValueIdCreator
+import org.grails.datastore.mapping.reflect.EntityReflector
 
 class SimpleIdBinderSpec extends HibernateGormDatastoreSpec {
 
@@ -129,4 +131,35 @@ class SimpleIdBinderSpec extends 
HibernateGormDatastoreSpec {
         then:
         thrown(org.hibernate.MappingException)
     }
+
+    def "bindSimpleId with synthetic identifier property"() {
+        given:
+        def mapping = Mock(org.grails.orm.hibernate.cfg.Mapping) {
+            isTablePerConcreteClass() >> false
+        }
+        def reflector = Mock(EntityReflector)
+        def domainClass = Mock(GrailsHibernatePersistentEntity) {
+            getMappedForm() >> mapping
+            getIdentity() >> null
+            getName() >> "TestEntity"
+            getMappingContext() >> 
getGrailsDomainBinder().hibernateMappingContext
+            getMapping() >> 
Mock(org.grails.datastore.mapping.model.ClassMapping)
+            getReflector() >> reflector
+        }
+        def rootClass = new RootClass(metadataBuildingContext)
+        currentTable = new Table("TEST_TABLE")
+        rootClass.setTable(currentTable)
+
+        when:
+        simpleIdBinder.bindSimpleId(domainClass, rootClass, new Identity(), 
rootClass.getTable())
+
+        then:
+        1 * simpleValueBinder.bindSimpleValue(_, null, _, "")
+        1 * propertyBinder.bindProperty(_, _)
+
+        rootClass.identifier instanceof BasicValue
+        rootClass.declaredIdentifierProperty != null
+        rootClass.identifierProperty != null
+        rootClass.table.primaryKey instanceof PrimaryKey
+    }
 }

Reply via email to