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 3e2f7d5e5fc70fee9c83e7cdff1ecd44cca97d5e Author: Walter Duque de Estrada <[email protected]> AuthorDate: Sun Feb 15 17:48:04 2026 -0600 Refactor NaturalId to return Optional<UniqueKey> and delegate side effects to the binder. --- .../org/grails/orm/hibernate/cfg/NaturalId.groovy | 31 +++++ .../binder/NaturalIdentifierBinder.java | 29 +--- .../grails/orm/hibernate/cfg/NaturalIdSpec.groovy | 93 +++++++++++++ .../NaturalIdentifierBinderSpec.groovy | 148 +++------------------ 4 files changed, 151 insertions(+), 150 deletions(-) diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/NaturalId.groovy b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/NaturalId.groovy index 852d254033..dbc8ffdf04 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/NaturalId.groovy +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/NaturalId.groovy @@ -18,6 +18,9 @@ package org.grails.orm.hibernate.cfg import groovy.transform.CompileStatic import groovy.transform.builder.Builder import groovy.transform.builder.SimpleStrategy +import org.grails.orm.hibernate.cfg.domainbinding.util.UniqueNameGenerator +import org.hibernate.mapping.PersistentClass +import org.hibernate.mapping.UniqueKey /** * @author Graeme Rocher @@ -34,4 +37,32 @@ class NaturalId { * Whether the natural id is mutable */ boolean mutable = false + + /** + * Creates the unique key for the natural identifier + * @param persistentClass The persistent class + * @return An Optional containing the UniqueKey if properties were found, otherwise empty + */ + Optional<UniqueKey> createUniqueKey(PersistentClass persistentClass) { + if (propertyNames == null || propertyNames.isEmpty()) { + return Optional.empty() + } + + UniqueKey uk = new UniqueKey(persistentClass.table) + int pks = 0 + for (String propertyName in propertyNames) { + if (persistentClass.hasProperty(propertyName)) { + def property = persistentClass.getProperty(propertyName) + property.setNaturalIdentifier(true) + property.setUpdatable(mutable) + uk.addColumns(property.value) + pks++ + } + } + + if (pks > 0) { + return Optional.of(uk) + } + return Optional.empty() + } } diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/NaturalIdentifierBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/NaturalIdentifierBinder.java index b27f4c1e8d..e932f253e8 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/NaturalIdentifierBinder.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/NaturalIdentifierBinder.java @@ -28,29 +28,10 @@ public class NaturalIdentifierBinder { public void bindNaturalIdentifier(Mapping mapping, PersistentClass persistentClass) { Optional.ofNullable(mapping.getIdentity()) .map(HibernateIdentity::getNatural) - .ifPresent(naturalId -> { - if(CollectionUtils.isEmpty(naturalId.getPropertyNames())) { - return; - } - var uk = new UniqueKey(); - uk.setTable(persistentClass.getTable()); - Stream<String> stringStream = naturalId.getPropertyNames() - .stream() - .filter(persistentClass::hasProperty); - List<String> list = stringStream.toList(); - Integer pks = list.stream() - .map(persistentClass::getProperty) - .map(property -> { - property.setNaturalIdentifier(true); - property.setUpdatable(naturalId.isMutable()); - uk.addColumns(property.getValue()); - return 1; - }) - .reduce(0, Integer::sum); - if (pks > 0) { - uniqueNameGenerator.setGeneratedUniqueName(uk); - persistentClass.getTable().addUniqueKey(uk); - } - }); + .flatMap(naturalId -> naturalId.createUniqueKey(persistentClass)) + .ifPresent(uk -> { + uniqueNameGenerator.setGeneratedUniqueName(uk); + persistentClass.getTable().addUniqueKey(uk); + }); } } diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/NaturalIdSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/NaturalIdSpec.groovy new file mode 100644 index 0000000000..12e0883a51 --- /dev/null +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/NaturalIdSpec.groovy @@ -0,0 +1,93 @@ +package org.grails.orm.hibernate.cfg + +import grails.gorm.specs.HibernateGormDatastoreSpec +import org.grails.orm.hibernate.cfg.domainbinding.util.UniqueNameGenerator +import org.hibernate.mapping.Column +import org.hibernate.mapping.Property +import org.hibernate.mapping.RootClass +import org.hibernate.mapping.Table +import org.hibernate.mapping.UniqueKey +import org.hibernate.mapping.Value + +class NaturalIdSpec extends HibernateGormDatastoreSpec { + + void "test createUniqueKey with a single property"() { + given: + def naturalId = new NaturalId(propertyNames: ["id1"], mutable: true) + def property = new Property() + property.name = "id1" + def value = Mock(Value) + property.value = value + def column = new Column("id1") + def table = new Table("test_table") + def rootClass = new RootClass(getGrailsDomainBinder().getMetadataBuildingContext()) + rootClass.addProperty(property) + rootClass.table = table + value.getSelectables() >> [column] + value.hasAnyUpdatableColumns() >> true + + when: + def result = naturalId.createUniqueKey(rootClass) + + then: + result.isPresent() + def uk = result.get() + uk.table == table + uk.columnSpan == 1 + property.isNaturalIdentifier() + property.isUpdateable() + } + + void "test createUniqueKey with composite property"() { + given: + def naturalId = new NaturalId(propertyNames: ["id1", "id2"], mutable: false) + def property1 = new Property() + property1.name = "id1" + def value1 = Mock(Value) + property1.value = value1 + def column1 = new Column("id1") + + def property2 = new Property() + property2.name = "id2" + def value2 = Mock(Value) + property2.value = value2 + def column2 = new Column("id2") + + def table = new Table("test_table") + def rootClass = new RootClass(getGrailsDomainBinder().getMetadataBuildingContext()) + rootClass.addProperty(property1) + rootClass.addProperty(property2) + rootClass.table = table + value1.getSelectables() >> [column1] + value1.hasAnyUpdatableColumns() >> false + value2.getSelectables() >> [column2] + value2.hasAnyUpdatableColumns() >> false + + when: + def result = naturalId.createUniqueKey(rootClass) + + then: + result.isPresent() + def uk = result.get() + uk.table == table + uk.columnSpan == 2 + property1.isNaturalIdentifier() + !property1.isUpdateable() + property2.isNaturalIdentifier() + !property2.isUpdateable() + } + + void "test createUniqueKey with empty property names"() { + given: + def naturalId = new NaturalId(propertyNames: [], mutable: false) + def table = new Table("test_table") + def rootClass = new RootClass(getGrailsDomainBinder().getMetadataBuildingContext()) + rootClass.table = table + + when: + def result = naturalId.createUniqueKey(rootClass) + + then: + result.isEmpty() + } +} diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/NaturalIdentifierBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/NaturalIdentifierBinderSpec.groovy index 6abb9c8a30..103ecb81b6 100644 --- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/NaturalIdentifierBinderSpec.groovy +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/NaturalIdentifierBinderSpec.groovy @@ -2,150 +2,53 @@ package org.grails.orm.hibernate.cfg.domainbinding import grails.gorm.specs.HibernateGormDatastoreSpec import org.grails.orm.hibernate.cfg.CompositeIdentity -import org.grails.orm.hibernate.cfg.Identity import org.grails.orm.hibernate.cfg.Mapping import org.grails.orm.hibernate.cfg.NaturalId - -import org.hibernate.mapping.Column -import org.hibernate.mapping.Property +import org.grails.orm.hibernate.cfg.domainbinding.binder.NaturalIdentifierBinder +import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateIdentity +import org.grails.orm.hibernate.cfg.domainbinding.util.UniqueNameGenerator import org.hibernate.mapping.RootClass import org.hibernate.mapping.Table import org.hibernate.mapping.UniqueKey -import org.hibernate.mapping.Value -import spock.lang.Unroll - -import org.grails.orm.hibernate.cfg.domainbinding.binder.NaturalIdentifierBinder -import org.grails.orm.hibernate.cfg.domainbinding.util.UniqueNameGenerator class NaturalIdentifierBinderSpec extends HibernateGormDatastoreSpec { - @Unroll("test bindNaturalIdentifier with a single property and mutable=#isMutable") - void "test bindNaturalIdentifier with a single property"(boolean isMutable) { + void "test bindNaturalIdentifier calls NaturalId.createUniqueKey and handles result"() { given: - def mapping = Mock(Mapping.class) - def identity = Mock(Identity) + def mapping = Mock(Mapping) + def identity = Mock(HibernateIdentity) def naturalId = Mock(NaturalId) - def property = new Property() - property.setName("id1") - def value = Mock(Value) - property.setValue(value) - Table table = Mock(Table) - def id1 = "id1" - mapping.getIdentity() >> identity - identity.getNatural() >> naturalId - naturalId.getPropertyNames() >> [id1] - naturalId.isMutable() >> isMutable + def uk = Mock(UniqueKey) + def table = Mock(Table) def rootClass = new RootClass(getGrailsDomainBinder().getMetadataBuildingContext()) - rootClass.setIdentifierProperty(property) rootClass.setTable(table) - value.getSelectables() >> [] - value.hasAnyUpdatableColumns() >> isMutable def uniqueNameGenerator = Mock(UniqueNameGenerator) def binder = new NaturalIdentifierBinder(uniqueNameGenerator) - when: - binder.bindNaturalIdentifier(mapping, rootClass) - - then: - 1 * uniqueNameGenerator.setGeneratedUniqueName(_) - property.isNaturalIdentifier() - property.isUpdatable() == isMutable - 1 * table.addUniqueKey(_) - - where: - isMutable << [true, false] - } - - void "test bindNaturalIdentifier with a composite (multi-property) natural id"() { - given: - def mapping = Mock(Mapping.class) - def identity = Mock(Identity) - def naturalId = Mock(NaturalId) - def property1 =new Property() - property1.setName("id1") - def value1 = Mock(Value) - property1.setValue(value1) - def column1 = new Column('id1') - def property2 = new Property() - property2.setName("id2") - def value2 = Mock(Value) - property2.setValue(value2) - def column2 = new Column('id2') - Table table = Mock(Table) - mapping.getIdentity() >> identity identity.getNatural() >> naturalId - naturalId.getPropertyNames() >> ["id1", "id2"] - naturalId.isMutable() >> true - - def rootClass = new RootClass(getGrailsDomainBinder().getMetadataBuildingContext()) - rootClass.setTable(table) - rootClass.addProperty(property1) - rootClass.addProperty(property2) - rootClass.getTable() >> table - - value1.getSelectables() >> [column1] - value2.getSelectables() >> [column2] - - def uniqueNameGenerator = Mock(UniqueNameGenerator) - def binder = new NaturalIdentifierBinder(uniqueNameGenerator) + naturalId.createUniqueKey(rootClass) >> Optional.of(uk) when: binder.bindNaturalIdentifier(mapping, rootClass) then: - property1.isNaturalIdentifier() - property2.isNaturalIdentifier() - 1 * table.addUniqueKey(_) >> { uks -> - def uk = uks.get(0) as UniqueKey - assert uk.getColumnSpan() == 2 - assert uk.getColumns().get(0) == column1 - assert uk.getColumns().get(1) == column2 - } + 1 * uniqueNameGenerator.setGeneratedUniqueName(uk) + 1 * table.addUniqueKey(uk) } - void "test bindNaturalIdentifier with CompositeIdentity"() { + void "test bindNaturalIdentifier when NaturalId returns empty result"() { given: - def mapping = Mock(Mapping.class) - def identity = Mock(CompositeIdentity) + def mapping = Mock(Mapping) + def identity = Mock(HibernateIdentity) def naturalId = Mock(NaturalId) - def property = new Property() - property.setName("id1") - def value = Mock(Value) - property.setValue(value) - Table table = Mock(Table) - def id1 = "id1" - mapping.getIdentity() >> identity - identity.getNatural() >> naturalId - naturalId.getPropertyNames() >> [id1] - naturalId.isMutable() >> false - def rootClass = new RootClass(getGrailsDomainBinder().getMetadataBuildingContext()) - rootClass.addProperty(property) - rootClass.setTable(table) - value.getSelectables() >> [] - - def uniqueNameGenerator = Mock(UniqueNameGenerator) - def binder = new NaturalIdentifierBinder(uniqueNameGenerator) - - when: - binder.bindNaturalIdentifier(mapping, rootClass) - - then: - 1 * uniqueNameGenerator.setGeneratedUniqueName(_) - property.isNaturalIdentifier() - 1 * table.addUniqueKey(_) - } - - void "test bindNaturalIdentifier when no natural id is defined"() { - given: - def mapping = Mock(Mapping.class) - def identity = Mock(Identity) def rootClass = new RootClass(getGrailsDomainBinder().getMetadataBuildingContext()) def uniqueNameGenerator = Mock(UniqueNameGenerator) def binder = new NaturalIdentifierBinder(uniqueNameGenerator) mapping.getIdentity() >> identity - identity.getNatural() >> null + identity.getNatural() >> naturalId + naturalId.createUniqueKey(rootClass) >> Optional.empty() when: binder.bindNaturalIdentifier(mapping, rootClass) @@ -154,26 +57,19 @@ class NaturalIdentifierBinderSpec extends HibernateGormDatastoreSpec { 0 * uniqueNameGenerator._ } - void "test bindNaturalIdentifier when property not found"() { + void "test bindNaturalIdentifier when no identity is defined"() { given: - def mapping = Mock(Mapping.class) - def identity = Mock(Identity) - def naturalId = Mock(NaturalId) + def mapping = Mock(Mapping) def rootClass = new RootClass(getGrailsDomainBinder().getMetadataBuildingContext()) def uniqueNameGenerator = Mock(UniqueNameGenerator) def binder = new NaturalIdentifierBinder(uniqueNameGenerator) - Table table = Mock(Table) - mapping.getIdentity() >> identity - identity.getNatural() >> naturalId - naturalId.getPropertyNames() >> ["nonExistentProperty"] - rootClass.setTable(table) + + mapping.getIdentity() >> null when: binder.bindNaturalIdentifier(mapping, rootClass) then: - 0 * uniqueNameGenerator.setGeneratedUniqueName(_) - 0 * table.addUniqueKey(_) + 0 * uniqueNameGenerator._ } - -} \ No newline at end of file +}
