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 ac81ba4c1694607440c9dfc13d27a9086a1fdf41 Author: Walter Duque de Estrada <[email protected]> AuthorDate: Tue Mar 17 17:13:21 2026 -0500 hibernate 7: refactor EnumTypeBinder --- .../cfg/domainbinding/binder/EnumTypeBinder.java | 10 +- .../domainbinding/binder/GrailsPropertyBinder.java | 2 +- .../secondpass/BasicCollectionElementBinder.java | 2 +- .../cfg/domainbinding/EnumTypeBinderSpec.groovy | 163 +++++++-------------- .../BasicCollectionElementBinderSpec.groovy | 2 +- .../datastore/mapping/model/MappingFactory.java | 7 +- 6 files changed, 60 insertions(+), 126 deletions(-) diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/EnumTypeBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/EnumTypeBinder.java index 0f4cfd99e2..c6f4a607e0 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/EnumTypeBinder.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/EnumTypeBinder.java @@ -71,18 +71,16 @@ public class EnumTypeBinder { private static final Logger LOG = LoggerFactory.getLogger(EnumTypeBinder.class); - public BasicValue bindEnumType( - @Nonnull HibernateEnumProperty property, Class<?> propertyType, String path) { + public BasicValue bindEnumType(@Nonnull HibernateEnumProperty property, String path) { String columnName = columnNameForPropertyAndPathFetcher.getColumnNameForPropertyAndPath(property, path, null); BasicValue simpleValue = new BasicValue(metadataBuildingContext, property.getTable()); - bindEnumType(property, propertyType, simpleValue, columnName); + bindEnumType(property, property.getType(), simpleValue, columnName); return simpleValue; } - public BasicValue bindEnumTypeForColumn( - @Nonnull HibernateToManyProperty property, Class<?> propertyType, @Nonnull String columnName) { + public BasicValue bindEnumTypeForColumn(@Nonnull HibernateToManyProperty property, @Nonnull String columnName) { BasicValue simpleValue = new BasicValue(metadataBuildingContext, property.getTable()); - bindEnumType(property, propertyType, simpleValue, columnName); + bindEnumType(property, property.getComponentType(), simpleValue, columnName); return simpleValue; } diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/GrailsPropertyBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/GrailsPropertyBinder.java index faa0c85feb..ba19cfe1f7 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/GrailsPropertyBinder.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/GrailsPropertyBinder.java @@ -75,7 +75,7 @@ public class GrailsPropertyBinder { // 1. Create Value and apply binders (consolidated block) if (currentGrailsProp instanceof HibernateEnumProperty hibernateEnumProperty) { - value = enumTypeBinder.bindEnumType(hibernateEnumProperty, currentGrailsProp.getType(), path); + value = enumTypeBinder.bindEnumType(hibernateEnumProperty, path); } else if (currentGrailsProp instanceof HibernateOneToOneProperty oneToOne && oneToOne.isValidHibernateOneToOne()) { value = oneToOneBinder.bindOneToOne(oneToOne, path); diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/BasicCollectionElementBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/BasicCollectionElementBinder.java index 80870f4fa8..e152f2b8e2 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/BasicCollectionElementBinder.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/BasicCollectionElementBinder.java @@ -81,7 +81,7 @@ public class BasicCollectionElementBinder { : new BackticksRemover().apply(prop) + UNDERSCORE + new BackticksRemover().apply(clazz); } if (isEnum) { - return enumTypeBinder.bindEnumTypeForColumn(property, referencedType, columnName); + return enumTypeBinder.bindEnumTypeForColumn(property, columnName); } else { String typeName = property.getTypeName(referencedType); BasicValue element = simpleValueColumnBinder.bindSimpleValue( diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/EnumTypeBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/EnumTypeBinderSpec.groovy index 7b3561c451..25c2941784 100644 --- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/EnumTypeBinderSpec.groovy +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/EnumTypeBinderSpec.groovy @@ -1,22 +1,3 @@ -/* - * 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 grails.gorm.specs.HibernateGormDatastoreSpec @@ -31,8 +12,6 @@ import org.grails.orm.hibernate.cfg.domainbinding.util.BackticksRemover import org.grails.orm.hibernate.cfg.domainbinding.util.ColumnNameForPropertyAndPathFetcher import org.grails.orm.hibernate.cfg.domainbinding.util.DefaultColumnNameFetcher import org.hibernate.engine.spi.SharedSessionContractImplementor -import org.hibernate.mapping.BasicValue -import org.hibernate.mapping.Column import org.hibernate.mapping.Table import org.hibernate.mapping.RootClass import org.hibernate.usertype.UserType @@ -65,11 +44,7 @@ class EnumTypeBinderSpec extends HibernateGormDatastoreSpec { binder = new EnumTypeBinder(metadataBuildingContext, columnNameFetcher, indexBinder, columnBinder) } - /** - * Helper to prevent the NullPointerException by linking the GORM entity - * to a Hibernate RootClass/Table. - */ - private PersistentProperty setupEntity(Class clazz, Table table) { + private PersistentProperty setupProperty(Class clazz, String propertyName, Table table) { def grailsDomainBinder = getGrailsDomainBinder() def owner = createPersistentEntity(clazz, grailsDomainBinder) as GrailsHibernatePersistentEntity @@ -77,122 +52,73 @@ class EnumTypeBinderSpec extends HibernateGormDatastoreSpec { rootClass.setTable(table) owner.setPersistentClass(rootClass) - return owner.getPropertyByName("status") + return owner.getPropertyByName(propertyName) + } + + def "should bind enum type for a collection element"() { + given: "An entity with a collection of enums" + def table = new Table("person_statuses") + def property = setupProperty(PersonWithCollection, "statuses", table) + + expect: "The property is a ToMany property" + property instanceof HibernateToManyProperty + + when: "the enum is bound for the collection column" + // This will now successfully call property.getComponentType() internally + def result = binder.bindEnumTypeForColumn(property as HibernateToManyProperty, "status_name") + + then: "The BasicValue is configured correctly" + result.getEnumerationStyle() == EnumType.STRING + result.getTypeParameters().getProperty(GrailsDomainBinder.ENUM_CLASS_PROP) == Status01.name } @Unroll def "should bind enum type as #expectedHibernateType when mapping specifies enumType as '#enumTypeMapping'"() { given: "A root entity and its enum property" def table = new Table("person") - PersistentProperty property = setupEntity(clazz, table) + def property = setupProperty(clazz, "status", table) - when: "the enum is bound" - def simpleValue = binder.bindEnumTypeForColumn(property as HibernateToManyProperty, Status01, "status_col") + when: "the enum is bound via the standard path" + def simpleValue = binder.bindEnumType(property as HibernateEnumProperty, "") then: "the correct hibernate type is set" simpleValue.getTypeName() == expectedHibernateType simpleValue.getEnumerationStyle() == expectedEnumStyle simpleValue.isNullable() == nullable - and: "the enum class property is always set" - simpleValue.getTypeParameters().getProperty(GrailsDomainBinder.ENUM_CLASS_PROP) == Status01.name - where: - clazz | enumTypeMapping | expectedHibernateType | expectedEnumStyle | nullable - Person01 | "default" | null | EnumType.STRING | false - Person02 | "string" | null | EnumType.STRING | true - Person03 | "ordinal" | null | EnumType.ORDINAL | true - Person04 | "identity" | IdentityEnumType.class.getName() | null | false - Person05 | UserTypeEnumType | UserTypeEnumType.class.getName() | null | false + clazz | enumTypeMapping | expectedHibernateType | expectedEnumStyle | nullable + Person01 | "default" | null | EnumType.STRING | false + Person02 | "string" | null | EnumType.STRING | true + Person03 | "ordinal" | null | EnumType.ORDINAL | true + Person04 | "identity" | IdentityEnumType.class.getName() | null | false + Person05 | UserTypeEnumType | UserTypeEnumType.class.getName() | null | false } @Unroll def "should set column nullability"() { given: "A root entity and its enum property" def table = new Table("person") - def columnName = "status_col" - PersistentProperty property = setupEntity(clazz, table) + def property = setupProperty(clazz, "status", table) when: "the enum is bound" - def simpleValue = binder.bindEnumTypeForColumn(property as HibernateToManyProperty, Status01, columnName) + def simpleValue = binder.bindEnumType(property as HibernateEnumProperty, "") then: - table.columns.size() == 1 - table.columns[0] == simpleValue.getColumn() - table.columns[0].isNullable() == nullable - table.columns[0].getValue() == simpleValue - table.columns[0].getName() == columnName + simpleValue.getColumns()[0].isNullable() == nullable where: clazz | nullable Person01 | false Person02 | true Clown01 | true - Clown02 | true - Clown03 | true - } - - @Unroll - def "should bind index and column constraints only when a column config is present"() { - given: "A root entity and its enum property" - def table = new Table("person") - def columnName = "status_col" - PersistentProperty property = setupEntity(clazz, table) - - when: "the enum is bound" - binder.bindEnumTypeForColumn(property as HibernateToManyProperty, Status01, columnName) - - then: "the index and column binders are invoked the correct number of times" - times * indexBinder.bindIndex(columnName, _ as Column, _, table) - times * columnBinder.bindColumnConfigToColumn(_ as Column, _, _) - - where: - clazz | times - Person01 | 0 - Person02 | 1 - } - - def "should create BasicValue and bind enum type"() { - given: "A root entity and its enum property" - def table = new Table("person") - PersistentProperty property = setupEntity(Person01, table) - - when: "the enum is bound using the new signature" - def result = binder.bindEnumType(property as HibernateEnumProperty, Status01, "") - - then: "a BasicValue is returned and bound correctly" - result instanceof BasicValue - result.getTable() == table - result.getTypeName() == null - result.getEnumerationStyle() == EnumType.STRING - result.getColumns().size() == 1 - result.getColumns()[0].getName() == "status" } } // --- Supporting Classes --- -class UserTypeEnumType implements UserType { - @Override int getSqlType() { 0 } - @Override Class returnedClass() { null } - @Override boolean equals(Object x, Object y) { false } - @Override int hashCode(Object x) { 0 } - @Override Object nullSafeGet(ResultSet rs, int position, SharedSessionContractImplementor session, Object owner) throws SQLException { null } - @Override void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws SQLException {} - @Override Object deepCopy(Object value) { null } - @Override boolean isMutable() { false } - @Override Serializable disassemble(Object value) { null } - @Override Object assemble(Serializable cached, Object owner) { null } -} - enum Status01 { AVAILABLE, OUT_OF_STOCK } -enum Status02 { - FOO(3), BAR(5) - Long id - Status02(Long id) { this.id = id } -} - @Entity class Person01 { Long id; Status01 status } @Entity class Person02 { Long id; Status01 status @@ -200,16 +126,31 @@ enum Status02 { } @Entity class Person03 { Long id; Status01 status - static mapping = { status enumType: "ordinal", nullable: true; tablePerHierarchy false } + static mapping = { status enumType: "ordinal", nullable: true } } @Entity class Person04 { - Long id; Status02 status - static mapping = { status enumType: 'identity' } + Long id; Status01 status + static mapping = { status enumType: "identity" } } @Entity class Person05 { - Long id; Status02 status + Long id; Status01 status static mapping = { status type: UserTypeEnumType } } +@Entity class PersonWithCollection { + Long id + Set<Status01> statuses +} @Entity class Clown01 extends Person01 { String clownName } -@Entity class Clown02 extends Person02 { String clownName } -@Entity class Clown03 extends Person03 { String clownName } \ No newline at end of file + +class UserTypeEnumType implements UserType { + @Override int getSqlType() { 0 } + @Override Class returnedClass() { Status01 } + @Override boolean equals(Object x, Object y) { x == y } + @Override int hashCode(Object x) { x.hashCode() } + @Override Object nullSafeGet(ResultSet rs, int position, SharedSessionContractImplementor session, Object owner) throws SQLException { null } + @Override void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws SQLException {} + @Override Object deepCopy(Object value) { value } + @Override boolean isMutable() { false } + @Override Serializable disassemble(Object value) { (Serializable)value } + @Override Object assemble(Serializable cached, Object owner) { cached } +} \ No newline at end of file diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/BasicCollectionElementBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/BasicCollectionElementBinderSpec.groovy index f06343e511..2cace08b8c 100644 --- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/BasicCollectionElementBinderSpec.groovy +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/BasicCollectionElementBinderSpec.groovy @@ -78,7 +78,7 @@ class BasicCollectionElementBinderSpec extends HibernateGormDatastoreSpec { then: element != null // Corrected: Match the 3-argument signature (Property, Class, String) - 1 * enumTypeBinder.bindEnumTypeForColumn(property, BCEBStatus, _ as String) >> mockValue + 1 * enumTypeBinder.bindEnumTypeForColumn(property, _ as String) >> mockValue } } diff --git a/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/model/MappingFactory.java b/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/model/MappingFactory.java index 4e1f914a4a..3faf9a4482 100644 --- a/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/model/MappingFactory.java +++ b/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/model/MappingFactory.java @@ -153,12 +153,7 @@ public abstract class MappingFactory<R extends Entity, T extends Property> { private Map<Class, Collection<CustomTypeMarshaller>> typeConverterMap = new ConcurrentHashMap<>(); public void registerCustomType(CustomTypeMarshaller marshallerCustom) { - Collection<CustomTypeMarshaller> marshallers = typeConverterMap.get(marshallerCustom.getTargetType()); - if (marshallers == null) { - marshallers = new ConcurrentLinkedQueue<>(); - typeConverterMap.put(marshallerCustom.getTargetType(), marshallers); - } - marshallers.add(marshallerCustom); + typeConverterMap.computeIfAbsent(marshallerCustom.getTargetType(), k -> new ConcurrentLinkedQueue<>()).add(marshallerCustom); } public boolean isSimpleType(Class propType) {
