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 3210b94c54d98c5db2a92757d2f78fd5899148ba Author: Walter Duque de Estrada <[email protected]> AuthorDate: Wed Feb 18 07:45:11 2026 -0600 Refactor collection binding logic and align property hierarchy - Extract bindCollectionWithJoinTable into CollectionWithJoinTableBinder - Update CollectionForPropertyConfigBinder to accept HibernateToManyProperty directly - Modularize CollectionSecondPassBinder and CollectionBinder via constructor injection - Ensure HibernateBasicProperty and HibernateEmbeddedCollectionProperty implement HibernateToManyProperty for consistent treatment of all collections --- .../cfg/domainbinding/binder/CollectionBinder.java | 14 +- .../binder/CollectionForPropertyConfigBinder.java | 23 ++-- .../hibernate/HibernateBasicProperty.java | 2 +- .../HibernateEmbeddedCollectionProperty.java | 2 +- .../secondpass/CollectionSecondPassBinder.java | 105 ++------------- .../secondpass/CollectionWithJoinTableBinder.java | 144 +++++++++++++++++++++ .../secondpass/ListSecondPassBinder.java | 4 +- .../domainbinding/secondpass/MapSecondPass.java | 2 +- .../secondpass/MapSecondPassBinder.java | 4 +- .../CollectionForPropertyConfigBinderSpec.groovy | 39 +----- .../CollectionWithJoinTableBinderSpec.groovy | 101 +++++++++++++++ 11 files changed, 290 insertions(+), 150 deletions(-) diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/CollectionBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/CollectionBinder.java index 16cecf44fc..aa045eb6e0 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/CollectionBinder.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/CollectionBinder.java @@ -30,6 +30,7 @@ import org.grails.orm.hibernate.cfg.domainbinding.collectionType.CollectionHolde import org.grails.orm.hibernate.cfg.domainbinding.collectionType.CollectionType; import org.grails.orm.hibernate.cfg.domainbinding.util.GrailsPropertyResolver; +import org.grails.orm.hibernate.cfg.domainbinding.secondpass.CollectionWithJoinTableBinder; import org.grails.orm.hibernate.cfg.domainbinding.secondpass.UnidirectionalOneToManyInverseValuesBinder; import org.hibernate.FetchMode; import org.hibernate.boot.spi.InFlightMetadataCollector; @@ -73,6 +74,8 @@ public class CollectionBinder { this.columnNameForPropertyAndPathFetcher = columnNameForPropertyAndPathFetcher; this.collectionHolder = collectionHolder; GrailsPropertyResolver grailsPropertyResolver = new GrailsPropertyResolver(); + CollectionForPropertyConfigBinder collectionForPropertyConfigBinder = new CollectionForPropertyConfigBinder(); + UnidirectionalOneToManyInverseValuesBinder unidirectionalOneToManyInverseValuesBinder = new UnidirectionalOneToManyInverseValuesBinder(); this.collectionSecondPassBinder = new CollectionSecondPassBinder( metadataBuildingContext, namingStrategy, @@ -87,7 +90,16 @@ public class CollectionBinder { grailsPropertyResolver, new BidirectionalOneToManyLinker(grailsPropertyResolver), new DependentKeyValueBinder(simpleValueBinder, compositeIdentifierToManyToOneBinder), - new UnidirectionalOneToManyInverseValuesBinder() + unidirectionalOneToManyInverseValuesBinder, + new CollectionWithJoinTableBinder( + metadataBuildingContext, + namingStrategy, + unidirectionalOneToManyInverseValuesBinder, + enumTypeBinder, + compositeIdentifierToManyToOneBinder, + simpleValueColumnFetcher, + collectionForPropertyConfigBinder + ) ); this.listSecondPassBinder = new ListSecondPassBinder(metadataBuildingContext, namingStrategy, collectionSecondPassBinder); this.mapSecondPassBinder = new MapSecondPassBinder(metadataBuildingContext, namingStrategy, collectionSecondPassBinder); diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/CollectionForPropertyConfigBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/CollectionForPropertyConfigBinder.java index 77d666af75..bff134fcd5 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/CollectionForPropertyConfigBinder.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/CollectionForPropertyConfigBinder.java @@ -1,24 +1,17 @@ package org.grails.orm.hibernate.cfg.domainbinding.binder; -import org.grails.orm.hibernate.cfg.PropertyConfig; +import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty; import org.hibernate.FetchMode; import org.hibernate.mapping.Collection; +import java.util.Optional; + +import jakarta.annotation.Nonnull; + public class CollectionForPropertyConfigBinder { - public void bindCollectionForPropertyConfig(Collection collection, PropertyConfig config) { - if (config == null) { - collection.setLazy(true); - collection.setExtraLazy(false); - } else { - final FetchMode fetch = config.getFetchMode(); - if(!fetch.equals(FetchMode.JOIN)) { - collection.setLazy(true); - } - final Boolean lazy = config.getLazy(); - if(lazy != null) { - collection.setExtraLazy(lazy); - } - } + public void bindCollectionForPropertyConfig(@Nonnull Collection collection,@Nonnull HibernateToManyProperty property) { + collection.setLazy(!FetchMode.JOIN.equals(property.getFetchMode())); + Optional.ofNullable(property.getLazy()).ifPresent(collection::setExtraLazy); } } diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateBasicProperty.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateBasicProperty.java index 36dde0aed1..8bfee6a6bb 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateBasicProperty.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateBasicProperty.java @@ -11,7 +11,7 @@ import java.beans.PropertyDescriptor; /** * Hibernate implementation of {@link org.grails.datastore.mapping.model.types.Basic} */ -public class HibernateBasicProperty extends BasicWithMapping<PropertyConfig> implements GrailsHibernatePersistentProperty { +public class HibernateBasicProperty extends BasicWithMapping<PropertyConfig> implements HibernateToManyProperty { public HibernateBasicProperty(GrailsHibernatePersistentEntity entity, MappingContext context, PropertyDescriptor property) { super(entity, context, property); } diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateEmbeddedCollectionProperty.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateEmbeddedCollectionProperty.java index e240bda39d..66e7a6c186 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateEmbeddedCollectionProperty.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateEmbeddedCollectionProperty.java @@ -11,7 +11,7 @@ import java.beans.PropertyDescriptor; /** * Hibernate implementation of {@link org.grails.datastore.mapping.model.types.EmbeddedCollection} */ -public class HibernateEmbeddedCollectionProperty extends EmbeddedCollectionWithMapping<PropertyConfig> implements GrailsHibernatePersistentProperty { +public class HibernateEmbeddedCollectionProperty extends EmbeddedCollectionWithMapping<PropertyConfig> implements HibernateToManyProperty { public HibernateEmbeddedCollectionProperty(PersistentEntity entity, MappingContext context, PropertyDescriptor property) { super(entity, context, property); } diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionSecondPassBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionSecondPassBinder.java index 1244486598..c326cd5690 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionSecondPassBinder.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionSecondPassBinder.java @@ -63,6 +63,7 @@ public class CollectionSecondPassBinder { private final BidirectionalOneToManyLinker bidirectionalOneToManyLinker; private final DependentKeyValueBinder dependentKeyValueBinder; private final UnidirectionalOneToManyInverseValuesBinder unidirectionalOneToManyInverseValuesBinder; + private final CollectionWithJoinTableBinder collectionWithJoinTableBinder; public CollectionSecondPassBinder( MetadataBuildingContext metadataBuildingContext, @@ -78,7 +79,8 @@ public class CollectionSecondPassBinder { GrailsPropertyResolver grailsPropertyResolver, BidirectionalOneToManyLinker bidirectionalOneToManyLinker, DependentKeyValueBinder dependentKeyValueBinder, - UnidirectionalOneToManyInverseValuesBinder unidirectionalOneToManyInverseValuesBinder) { + UnidirectionalOneToManyInverseValuesBinder unidirectionalOneToManyInverseValuesBinder, + CollectionWithJoinTableBinder collectionWithJoinTableBinder) { this.metadataBuildingContext = metadataBuildingContext; this.namingStrategy = namingStrategy; this.jdbcEnvironment = jdbcEnvironment; @@ -93,14 +95,15 @@ public class CollectionSecondPassBinder { this.bidirectionalOneToManyLinker = bidirectionalOneToManyLinker; this.dependentKeyValueBinder = dependentKeyValueBinder; this.unidirectionalOneToManyInverseValuesBinder = unidirectionalOneToManyInverseValuesBinder; + this.collectionWithJoinTableBinder = collectionWithJoinTableBinder; this.defaultColumnNameFetcher = new DefaultColumnNameFetcher(namingStrategy); this.orderByClauseBuilder = new OrderByClauseBuilder(); } - public void bindCollectionSecondPass(HibernateToManyProperty property, @Nonnull InFlightMetadataCollector mappings, - Map<?, ?> persistentClasses, Collection collection) { + public void bindCollectionSecondPass(@Nonnull HibernateToManyProperty property, @Nonnull InFlightMetadataCollector mappings, + Map<?, ?> persistentClasses, @Nonnull Collection collection) { PersistentClass associatedClass = null; if (LOG.isDebugEnabled()) @@ -157,7 +160,7 @@ public class CollectionSecondPassBinder { collection.setCollectionTable(associatedClass.getTable()); } - new CollectionForPropertyConfigBinder().bindCollectionForPropertyConfig(collection, propConfig); + new CollectionForPropertyConfigBinder().bindCollectionForPropertyConfig(collection, property); } final boolean isManyToMany = property instanceof HibernateManyToManyProperty; @@ -227,7 +230,7 @@ public class CollectionSecondPassBinder { ManyToOne element = manyToOneBinder.bindManyToOne((Association)otherSide, collection.getCollectionTable(), EMPTY_PATH); element.setReferencedEntityName(otherSide.getOwner().getName()); collection.setElement(element); - new CollectionForPropertyConfigBinder().bindCollectionForPropertyConfig(collection, propConfig); + new CollectionForPropertyConfigBinder().bindCollectionForPropertyConfig(collection, property); if (property.isCircular()) { collection.setInverse(false); } @@ -239,12 +242,12 @@ public class CollectionSecondPassBinder { // there are problems with list and map mappings and join columns relating to duplicate key constraints // TODO change this when HHH-1268 is resolved if (!property.shouldBindWithForeignKey()) { - bindCollectionWithJoinTable(property, mappings, collection, propConfig); + collectionWithJoinTableBinder.bindCollectionWithJoinTable(property, mappings, collection); } else { bindUnidirectionalOneToMany((HibernateOneToManyProperty) property, mappings, collection); } } else if (property.supportsJoinColumnMapping()) { - bindCollectionWithJoinTable(property, mappings, collection, propConfig); + collectionWithJoinTableBinder.bindCollectionWithJoinTable(property, mappings, collection); } collectionKeyColumnUpdater.forceNullableAndCheckUpdatable(key, property); // Use the injected service } @@ -276,95 +279,7 @@ public class CollectionSecondPassBinder { referenced.addProperty(prop); } - private void bindCollectionWithJoinTable(HibernateToManyProperty property, - @Nonnull InFlightMetadataCollector mappings, Collection collection, PropertyConfig config) { - collection.setInverse(false); - SimpleValue element; - final boolean isBasicCollectionType = property instanceof Basic; - if (isBasicCollectionType) { - element = new BasicValue(metadataBuildingContext, collection.getCollectionTable()); - } - else { - // for a normal unidirectional one-to-many we use a join column - element = new ManyToOne(metadataBuildingContext, collection.getCollectionTable()); - unidirectionalOneToManyInverseValuesBinder.bindUnidirectionalOneToManyInverseValues(property, (ManyToOne) element); - } - - String columnName; - - var joinColumnMappingOptional = Optional.ofNullable(config).map(PropertyConfig::getJoinTableColumnConfig); - if (isBasicCollectionType) { - final Class<?> referencedType = property.getType(); - String className = referencedType.getName(); - final boolean isEnum = referencedType.isEnum(); - if (joinColumnMappingOptional.isPresent()) { - columnName = joinColumnMappingOptional.get().getName(); - } - else { - var clazz = namingStrategy.resolveColumnName(className); - var prop = namingStrategy.resolveTableName(property.getName()); - columnName = isEnum ? clazz : new BackticksRemover().apply(prop) + UNDERSCORE + new BackticksRemover().apply(clazz); - } - - if (isEnum) { - enumTypeBinder.bindEnumType(property, referencedType, element, columnName); - } - else { - - Mapping mapping = null; - GrailsHibernatePersistentEntity domainClass = property.getHibernateOwner(); - if (domainClass != null) { - mapping = domainClass.getMappedForm(); - } - String typeName = property.getTypeName(); - if (typeName == null) { - Type type = mappings.getTypeConfiguration().getBasicTypeRegistry().getRegisteredType(className); - if (type != null) { - typeName = type.getName(); - } - } - if (typeName == null) { - String domainName = property.getHibernateOwner().getName(); - throw new MappingException("Missing type or column for column["+columnName+"] on domain["+domainName+"] referencing["+className+"]"); - } - - new SimpleValueColumnBinder().bindSimpleValue(element, typeName, columnName, true); - if (joinColumnMappingOptional.isPresent()) { - Column column = simpleValueColumnFetcher.getColumnForSimpleValue(element); - ColumnConfig columnConfig = joinColumnMappingOptional.get(); - final PropertyConfig mappedForm = property.getMappedForm(); - new ColumnConfigToColumnBinder().bindColumnConfigToColumn(column, columnConfig, mappedForm); - } - } - } else { - final GrailsHibernatePersistentEntity domainClass = property.getHibernateAssociatedEntity(); - - Mapping m = null; - if (domainClass != null) { - m = domainClass.getMappedForm(); - } - if (m != null && m.hasCompositeIdentifier()) { - CompositeIdentity ci = (CompositeIdentity) m.getIdentity(); - compositeIdentifierToManyToOneBinder.bindCompositeIdentifierToManyToOne(property, element, ci, domainClass, EMPTY_PATH); - } - else { - if (joinColumnMappingOptional.isPresent()) { - columnName = joinColumnMappingOptional.get().getName(); - } - else { - var decapitalize = domainClass.getHibernateRootEntity().getJavaClass().getSimpleName(); - columnName = namingStrategy.resolveColumnName(decapitalize) + FOREIGN_KEY_SUFFIX; - } - - new SimpleValueColumnBinder().bindSimpleValue(element, "long", columnName, true); - } - } - - collection.setElement(element); - - new CollectionForPropertyConfigBinder().bindCollectionForPropertyConfig(collection, config); - } diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionWithJoinTableBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionWithJoinTableBinder.java new file mode 100644 index 0000000000..ea8ca64583 --- /dev/null +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionWithJoinTableBinder.java @@ -0,0 +1,144 @@ +package org.grails.orm.hibernate.cfg.domainbinding.secondpass; + +import jakarta.annotation.Nonnull; +import org.grails.datastore.mapping.model.types.Basic; +import org.grails.orm.hibernate.cfg.*; +import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty; +import org.grails.orm.hibernate.cfg.domainbinding.util.BackticksRemover; +import org.grails.orm.hibernate.cfg.domainbinding.binder.CollectionForPropertyConfigBinder; +import org.grails.orm.hibernate.cfg.domainbinding.binder.ColumnConfigToColumnBinder; +import org.grails.orm.hibernate.cfg.domainbinding.binder.CompositeIdentifierToManyToOneBinder; +import org.grails.orm.hibernate.cfg.domainbinding.binder.EnumTypeBinder; +import org.grails.orm.hibernate.cfg.domainbinding.binder.SimpleValueColumnBinder; +import org.grails.orm.hibernate.cfg.domainbinding.util.SimpleValueColumnFetcher; +import org.hibernate.MappingException; +import org.hibernate.boot.spi.InFlightMetadataCollector; +import org.hibernate.boot.spi.MetadataBuildingContext; +import org.hibernate.mapping.*; +import org.hibernate.mapping.Collection; +import org.hibernate.type.Type; + +import java.util.Optional; + +import static org.grails.orm.hibernate.cfg.GrailsDomainBinder.*; + +/** + * Binds a collection with a join table. + */ +public class CollectionWithJoinTableBinder { + + private final MetadataBuildingContext metadataBuildingContext; + private final PersistentEntityNamingStrategy namingStrategy; + private final UnidirectionalOneToManyInverseValuesBinder unidirectionalOneToManyInverseValuesBinder; + private final EnumTypeBinder enumTypeBinder; + private final CompositeIdentifierToManyToOneBinder compositeIdentifierToManyToOneBinder; + private final SimpleValueColumnFetcher simpleValueColumnFetcher; + private final CollectionForPropertyConfigBinder collectionForPropertyConfigBinder; + + public CollectionWithJoinTableBinder( + MetadataBuildingContext metadataBuildingContext, + PersistentEntityNamingStrategy namingStrategy, + UnidirectionalOneToManyInverseValuesBinder unidirectionalOneToManyInverseValuesBinder, + EnumTypeBinder enumTypeBinder, + CompositeIdentifierToManyToOneBinder compositeIdentifierToManyToOneBinder, + SimpleValueColumnFetcher simpleValueColumnFetcher, + CollectionForPropertyConfigBinder collectionForPropertyConfigBinder) { + this.metadataBuildingContext = metadataBuildingContext; + this.namingStrategy = namingStrategy; + this.unidirectionalOneToManyInverseValuesBinder = unidirectionalOneToManyInverseValuesBinder; + this.enumTypeBinder = enumTypeBinder; + this.compositeIdentifierToManyToOneBinder = compositeIdentifierToManyToOneBinder; + this.simpleValueColumnFetcher = simpleValueColumnFetcher; + this.collectionForPropertyConfigBinder = collectionForPropertyConfigBinder; + } + + public void bindCollectionWithJoinTable(@Nonnull HibernateToManyProperty property, + @Nonnull InFlightMetadataCollector mappings, @Nonnull Collection collection) { + + collection.setInverse(false); + SimpleValue element; + final boolean isBasicCollectionType = property instanceof Basic; + if (isBasicCollectionType) { + element = new BasicValue(metadataBuildingContext, collection.getCollectionTable()); + } + else { + // for a normal unidirectional one-to-many we use a join column + element = new ManyToOne(metadataBuildingContext, collection.getCollectionTable()); + unidirectionalOneToManyInverseValuesBinder.bindUnidirectionalOneToManyInverseValues(property, (ManyToOne) element); + } + + String columnName; + + var joinColumnMappingOptional = Optional.ofNullable(property.getMappedForm()).map(PropertyConfig::getJoinTableColumnConfig); + if (isBasicCollectionType) { + final Class<?> referencedType = ((Basic) property).getComponentType(); + String className = referencedType.getName(); + final boolean isEnum = referencedType.isEnum(); + if (joinColumnMappingOptional.isPresent()) { + columnName = joinColumnMappingOptional.get().getName(); + } + else { + var clazz = namingStrategy.resolveColumnName(className); + var prop = namingStrategy.resolveTableName(property.getName()); + columnName = isEnum ? clazz : new BackticksRemover().apply(prop) + UNDERSCORE + new BackticksRemover().apply(clazz); + } + + if (isEnum) { + enumTypeBinder.bindEnumType(property, referencedType, element, columnName); + } + else { + + Mapping mapping = null; + GrailsHibernatePersistentEntity domainClass = property.getHibernateOwner(); + if (domainClass != null) { + mapping = domainClass.getMappedForm(); + } + String typeName = property.getTypeName(); + if (typeName == null) { + Type type = mappings.getTypeConfiguration().getBasicTypeRegistry().getRegisteredType(className); + if (type != null) { + typeName = type.getName(); + } + } + if (typeName == null) { + String domainName = property.getHibernateOwner().getName(); + throw new MappingException("Missing type or column for column["+columnName+"] on domain["+domainName+"] referencing["+className+"]"); + } + + new SimpleValueColumnBinder().bindSimpleValue(element, typeName, columnName, true); + if (joinColumnMappingOptional.isPresent()) { + Column column = simpleValueColumnFetcher.getColumnForSimpleValue(element); + ColumnConfig columnConfig = joinColumnMappingOptional.get(); + final PropertyConfig mappedForm = property.getMappedForm(); + new ColumnConfigToColumnBinder().bindColumnConfigToColumn(column, columnConfig, mappedForm); + } + } + } else { + final GrailsHibernatePersistentEntity domainClass = property.getHibernateAssociatedEntity(); + + Mapping m = null; + if (domainClass != null) { + m = domainClass.getMappedForm(); + } + if (m != null && m.hasCompositeIdentifier()) { + CompositeIdentity ci = (CompositeIdentity) m.getIdentity(); + compositeIdentifierToManyToOneBinder.bindCompositeIdentifierToManyToOne(property, element, ci, domainClass, EMPTY_PATH); + } + else { + if (joinColumnMappingOptional.isPresent()) { + columnName = joinColumnMappingOptional.get().getName(); + } + else { + var decapitalize = domainClass.getHibernateRootEntity().getJavaClass().getSimpleName(); + columnName = namingStrategy.resolveColumnName(decapitalize) + FOREIGN_KEY_SUFFIX; + } + + new SimpleValueColumnBinder().bindSimpleValue(element, "long", columnName, true); + } + } + + collection.setElement(element); + + collectionForPropertyConfigBinder.bindCollectionForPropertyConfig(collection, property); + } +} diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/ListSecondPassBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/ListSecondPassBinder.java index 85f5d08d33..a618bb87b6 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/ListSecondPassBinder.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/ListSecondPassBinder.java @@ -44,8 +44,8 @@ public class ListSecondPassBinder { this.namingStrategy = namingStrategy; } - public void bindListSecondPass(HibernateToManyProperty property, @Nonnull InFlightMetadataCollector mappings, - Map<?, ?> persistentClasses, List list) { + public void bindListSecondPass(@Nonnull HibernateToManyProperty property, @Nonnull InFlightMetadataCollector mappings, + Map<?, ?> persistentClasses, @Nonnull List list) { collectionSecondPassBinder.bindCollectionSecondPass(property, mappings, persistentClasses, list); String columnName = property.getIndexColumnName(namingStrategy); diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/MapSecondPass.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/MapSecondPass.java index 7574c35a7f..610b8747b3 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/MapSecondPass.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/MapSecondPass.java @@ -21,7 +21,7 @@ public class MapSecondPass implements org.hibernate.boot.spi.SecondPass, GrailsS protected final Collection collection; public MapSecondPass(MapSecondPassBinder mapSecondPassBinder, HibernateToManyProperty property, @Nonnull InFlightMetadataCollector mappings, - Collection coll) { + @Nonnull Collection coll) { this.mapSecondPassBinder = mapSecondPassBinder; this.property = property; this.mappings = mappings; diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/MapSecondPassBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/MapSecondPassBinder.java index ab462f3242..1fe67ab891 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/MapSecondPassBinder.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/MapSecondPassBinder.java @@ -41,8 +41,8 @@ public class MapSecondPassBinder { this.collectionSecondPassBinder = collectionSecondPassBinder; } - public void bindMapSecondPass(HibernateToManyProperty property, @Nonnull InFlightMetadataCollector mappings, - Map<?, ?> persistentClasses, org.hibernate.mapping.Map map) { + public void bindMapSecondPass(@Nonnull HibernateToManyProperty property, @Nonnull InFlightMetadataCollector mappings, + Map<?, ?> persistentClasses, @Nonnull org.hibernate.mapping.Map map) { collectionSecondPassBinder.bindCollectionSecondPass(property, mappings, persistentClasses, map); SimpleValue value = new BasicValue(metadataBuildingContext, map.getCollectionTable()); diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionForPropertyConfigBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionForPropertyConfigBinderSpec.groovy index 35cc9b0b84..ab3b095d53 100644 --- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionForPropertyConfigBinderSpec.groovy +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CollectionForPropertyConfigBinderSpec.groovy @@ -1,7 +1,7 @@ package org.grails.orm.hibernate.cfg.domainbinding import grails.gorm.specs.HibernateGormDatastoreSpec -import org.grails.orm.hibernate.cfg.PropertyConfig +import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty import org.hibernate.FetchMode import org.hibernate.mapping.RootClass import org.hibernate.mapping.Set @@ -17,40 +17,24 @@ class CollectionForPropertyConfigBinderSpec extends HibernateGormDatastoreSpec { - def "should apply default settings when the property config is null"() { - def grailsDomainBinder = getGrailsDomainBinder() - given: "A hibernate collection" - def owner = new RootClass(grailsDomainBinder.metadataBuildingContext) - def collection = new Set(grailsDomainBinder.metadataBuildingContext, owner) - // Set initial state to be different from the expected outcome - collection.setLazy(false) - collection.setExtraLazy(true) - - when: "the binder is called with a null config" - binder.bindCollectionForPropertyConfig(collection, null) - - then: "specific default values are applied" - collection.isLazy() - !collection.isExtraLazy() - } @Unroll def "should bind lazy settings based on fetch mode '#fetchMode.name()'} and an explicit lazy config of #lazySetting"() { - given: "A hibernate collection and a mocked property config" + given: "A hibernate collection and a mocked property" def owner = new RootClass(grailsDomainBinder.metadataBuildingContext) def collection = new Set(grailsDomainBinder.metadataBuildingContext, owner) - def config = Mock(PropertyConfig) + def property = Mock(HibernateToManyProperty) // Set initial state collection.setLazy(false) collection.setExtraLazy(false) - and: "the config is stubbed" - config.getFetchMode() >> fetchMode - config.getLazy() >> lazySetting + and: "the property is stubbed" + property.getFetchMode() >> fetchMode + property.getLazy() >> lazySetting when: "the binder is applied" - binder.bindCollectionForPropertyConfig(collection, config) + binder.bindCollectionForPropertyConfig(collection, property) then: "the collection's lazy and extraLazy properties are set according to the binder's logic" collection.isLazy() == expectedIsLazy @@ -67,15 +51,6 @@ class CollectionForPropertyConfigBinderSpec extends HibernateGormDatastoreSpec { // FetchMode.SUBSELECT | true || true | true } - def "should not throw an exception if the collection itself is null"() { - given: "A valid property config" - def config = Mock(PropertyConfig) - when: "the binder is called with a null collection" - binder.bindCollectionForPropertyConfig(null, config) - - then: "a NullPointerException is thrown because the code does not guard against it" - thrown(NullPointerException) - } } \ No newline at end of file diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionWithJoinTableBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionWithJoinTableBinderSpec.groovy new file mode 100644 index 0000000000..27ff1401ab --- /dev/null +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionWithJoinTableBinderSpec.groovy @@ -0,0 +1,101 @@ +package org.grails.orm.hibernate.cfg.domainbinding.secondpass + +import grails.gorm.annotation.Entity +import grails.gorm.specs.HibernateGormDatastoreSpec +import org.grails.datastore.mapping.model.PersistentEntity +import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentProperty +import org.grails.orm.hibernate.cfg.domainbinding.binder.CollectionForPropertyConfigBinder +import org.grails.orm.hibernate.cfg.domainbinding.binder.CompositeIdentifierToManyToOneBinder +import org.grails.orm.hibernate.cfg.domainbinding.binder.EnumTypeBinder +import org.grails.orm.hibernate.cfg.domainbinding.util.SimpleValueColumnFetcher +import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty +import org.hibernate.boot.spi.InFlightMetadataCollector +import org.hibernate.mapping.Collection +import org.hibernate.mapping.ManyToOne +import org.hibernate.mapping.RootClass +import org.hibernate.mapping.Set +import org.hibernate.mapping.Table +import org.hibernate.type.spi.TypeConfiguration +import spock.lang.Subject + +class CollectionWithJoinTableBinderSpec extends HibernateGormDatastoreSpec { + + @Subject + CollectionWithJoinTableBinder binder + + UnidirectionalOneToManyInverseValuesBinder unidirectionalOneToManyInverseValuesBinder = Mock(UnidirectionalOneToManyInverseValuesBinder) + EnumTypeBinder enumTypeBinder = Mock(EnumTypeBinder) + CompositeIdentifierToManyToOneBinder compositeIdentifierToManyToOneBinder = Mock(CompositeIdentifierToManyToOneBinder) + SimpleValueColumnFetcher simpleValueColumnFetcher = Mock(SimpleValueColumnFetcher) + CollectionForPropertyConfigBinder collectionForPropertyConfigBinder = Mock(CollectionForPropertyConfigBinder) + + void setup() { + def domainBinder = getGrailsDomainBinder() + binder = new CollectionWithJoinTableBinder( + domainBinder.metadataBuildingContext, + domainBinder.namingStrategy, + unidirectionalOneToManyInverseValuesBinder, + enumTypeBinder, + compositeIdentifierToManyToOneBinder, + simpleValueColumnFetcher, + collectionForPropertyConfigBinder + ) + } + + void "test bindCollectionWithJoinTable for basic type"() { + given: + PersistentEntity authorEntity = createPersistentEntity(CWJTBAuthor) + HibernateToManyProperty property = (HibernateToManyProperty) authorEntity.getPropertyByName("tags") + def domainBinder = getGrailsDomainBinder() + + InFlightMetadataCollector mappings = Mock(InFlightMetadataCollector) + mappings.getTypeConfiguration() >> new TypeConfiguration() + + def owner = new RootClass(domainBinder.metadataBuildingContext) + Collection collection = new Set(domainBinder.metadataBuildingContext, owner) + collection.setCollectionTable(new Table("CWJTB_TAGS")) + + when: + binder.bindCollectionWithJoinTable(property, mappings, collection) + + then: + collection.getElement() != null + 1 * collectionForPropertyConfigBinder.bindCollectionForPropertyConfig(collection, property) + } + + void "test bindCollectionWithJoinTable for entity association"() { + given: + createPersistentEntity(CWJTBBook) + PersistentEntity authorEntity = createPersistentEntity(CWJTBAuthor) + HibernateToManyProperty property = (HibernateToManyProperty) authorEntity.getPropertyByName("books") + def domainBinder = getGrailsDomainBinder() + + InFlightMetadataCollector mappings = Mock(InFlightMetadataCollector) + + def owner = new RootClass(domainBinder.metadataBuildingContext) + Collection collection = new Set(domainBinder.metadataBuildingContext, owner) + collection.setCollectionTable(new Table("CWJTB_BOOKS")) + + when: + binder.bindCollectionWithJoinTable(property, mappings, collection) + + then: + collection.getElement() instanceof ManyToOne + 1 * collectionForPropertyConfigBinder.bindCollectionForPropertyConfig(collection, property) + } +} + +@Entity +class CWJTBBook { + Long id + String title +} + +@Entity +class CWJTBAuthor { + Long id + String name + java.util.Set<CWJTBBook> books + java.util.Set<String> tags + static hasMany = [books: CWJTBBook, tags: String] +}
