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 + } }
