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 cc37cc3467b57fe41bdae5b1ca9394decc877941 Author: Walter Duque de Estrada <[email protected]> AuthorDate: Wed Mar 4 18:29:34 2026 -0600 cleanup(hibernate7): ColumnBinder using more GrailsHibernate types --- .../cfg/domainbinding/binder/ColumnBinder.java | 23 +++--------- .../hibernate/HibernateAssociation.java | 8 +++++ .../hibernate/HibernateManyToManyProperty.java | 5 +++ .../hibernate/HibernateOneToOneProperty.java | 9 +++++ .../HibernateOneToOnePropertySpec.groovy | 42 +++++++++++++++++++++- .../hibernate/HibernateToManyPropertySpec.groovy | 27 +++++++++++++- 6 files changed, 94 insertions(+), 20 deletions(-) diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/ColumnBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/ColumnBinder.java index d08d1b92a4..f6c5c053de 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/ColumnBinder.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/ColumnBinder.java @@ -18,14 +18,11 @@ */ package org.grails.orm.hibernate.cfg.domainbinding.binder; -import org.grails.datastore.mapping.model.types.Association; -import org.grails.datastore.mapping.model.types.ToOne; import org.grails.orm.hibernate.cfg.ColumnConfig; import org.grails.orm.hibernate.cfg.Mapping; import org.grails.orm.hibernate.cfg.PersistentEntityNamingStrategy; import org.grails.orm.hibernate.cfg.PropertyConfig; -import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateManyToManyProperty; -import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateOneToOneProperty; +import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateAssociation; import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernatePersistentProperty; import org.grails.orm.hibernate.cfg.domainbinding.util.BackticksRemover; import org.grails.orm.hibernate.cfg.domainbinding.util.ColumnNameForPropertyAndPathFetcher; @@ -81,9 +78,9 @@ public class ColumnBinder { * Binds a Column instance to the Hibernate meta model * * @param property The Grails domain class property - * @param parentProperty + * @param parentProperty parent property * @param column The column to bind - * @param path + * @param path the path * @param table The table name */ public void bindColumn( @@ -104,22 +101,12 @@ public class ColumnBinder { Class<?> userType = property.getUserType(); String columnName = columnNameForPropertyAndPathFetcher.getColumnNameForPropertyAndPath(property, path, cc); - if ((property instanceof Association association) && userType == null) { + if ((property instanceof HibernateAssociation assoc) && userType == null) { // Only use conventional naming when the column has not been explicitly mapped. if (column.getName() == null) { column.setName(columnName); } - if (property instanceof HibernateManyToManyProperty) { - column.setNullable(false); - } else if (property instanceof HibernateOneToOneProperty - && association.isBidirectional() - && !association.isOwningSide()) { - column.setNullable(!association.getInverseSide().isHasOne()); - } else if ((property instanceof ToOne) && association.isCircular()) { - column.setNullable(true); - } else { - column.setNullable(true); - } + column.setNullable(assoc.isAssociationColumnNullable()); } else { column.setName(columnName); column.setNullable( diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateAssociation.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateAssociation.java index 5d71d7aa6f..d5cb2885fc 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateAssociation.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateAssociation.java @@ -51,6 +51,14 @@ public interface HibernateAssociation extends HibernatePersistentProperty { boolean isBidirectionalOneToManyMap(); + /** + * Returns the nullable value for the FK column when this property is an association without a + * user type. The default is {@code true}; subtypes override for their specific semantics. + */ + default boolean isAssociationColumnNullable() { + return true; + } + // --- Hibernate-typed overrides, removing instanceof guards --- /** Returns the inverse side as a {@link HibernateAssociation}, eliminating cast at call sites. */ diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateManyToManyProperty.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateManyToManyProperty.java index d5d57d6e54..86ef2e79c4 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateManyToManyProperty.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateManyToManyProperty.java @@ -41,4 +41,9 @@ public class HibernateManyToManyProperty extends ManyToManyWithMapping<PropertyC public HibernateManyToManyProperty getHibernateInverseSide() { return (HibernateManyToManyProperty) getInverseSide(); } + + @Override + public boolean isAssociationColumnNullable() { + return false; + } } diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateOneToOneProperty.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateOneToOneProperty.java index ac85c3407f..166f8795c9 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateOneToOneProperty.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateOneToOneProperty.java @@ -110,4 +110,13 @@ public class HibernateOneToOneProperty extends OneToOneWithMapping<PropertyConfi validateAssociation(); return !isValidHibernateOneToOne(); } + + @Override + public boolean isAssociationColumnNullable() { + if (isBidirectional() && !isOwningSide()) { + HibernateOneToOneProperty inverseSide = getHibernateInverseSide(); + return inverseSide == null || !inverseSide.isHasOne(); + } + return true; + } } diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/HibernateOneToOnePropertySpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/HibernateOneToOnePropertySpec.groovy index d178cfa3d8..a9e1855566 100644 --- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/HibernateOneToOnePropertySpec.groovy +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/HibernateOneToOnePropertySpec.groovy @@ -28,7 +28,7 @@ import org.hibernate.type.ForeignKeyDirection class HibernateOneToOnePropertySpec extends HibernateGormDatastoreSpec { def setupSpec() { - manager.addAllDomainClasses([OneToOneFace, OneToOneNose]) + manager.addAllDomainClasses([OneToOneFace, OneToOneNose, OneToOneLeft, OneToOneRight]) } void "getHibernateInverseSide returns HibernateOneToOneProperty"() { @@ -132,6 +132,33 @@ class HibernateOneToOnePropertySpec extends HibernateGormDatastoreSpec { then: faceProp.needsSimpleValueBinding() } + + void "isAssociationColumnNullable is false when bidirectional non-owning and inverse has hasOne"() { + when: + def noseEntity = mappingContext.getPersistentEntity(OneToOneNose.name) + def faceProp = noseEntity.persistentProperties.find { it.name == 'face' } as HibernateOneToOneProperty + + then: + !faceProp.isAssociationColumnNullable() + } + + void "isAssociationColumnNullable is true when owning side declares hasOne"() { + when: + def faceEntity = mappingContext.getPersistentEntity(OneToOneFace.name) + def noseProp = faceEntity.persistentProperties.find { it.name == 'nose' } as HibernateOneToOneProperty + + then: + noseProp.isAssociationColumnNullable() + } + + void "isAssociationColumnNullable is true when bidirectional non-owning but inverse does not have hasOne"() { + when: + def leftEntity = mappingContext.getPersistentEntity(OneToOneLeft.name) + def rightProp = leftEntity.persistentProperties.find { it.name == 'right' } as HibernateOneToOneProperty + + then: + rightProp.isAssociationColumnNullable() + } } @Entity @@ -147,3 +174,16 @@ class OneToOneNose implements HibernateEntity<OneToOneNose> { OneToOneFace face static belongsTo = [face: OneToOneFace] } + +@Entity +class OneToOneRight implements HibernateEntity<OneToOneRight> { + String code + OneToOneLeft left +} + +@Entity +class OneToOneLeft implements HibernateEntity<OneToOneLeft> { + String label + OneToOneRight right + static belongsTo = [right: OneToOneRight] +} diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateToManyPropertySpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateToManyPropertySpec.groovy index d597720af5..aaca72d0d3 100644 --- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateToManyPropertySpec.groovy +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateToManyPropertySpec.groovy @@ -25,7 +25,7 @@ import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyPrope class HibernateToManyPropertySpec extends HibernateGormDatastoreSpec { def setupSpec() { - manager.addAllDomainClasses([HTMPBook, HTMPAuthor, HTMPAuthorCustom]) + manager.addAllDomainClasses([HTMPBook, HTMPAuthor, HTMPAuthorCustom, HTMPStudent, HTMPCourse]) } void "resolveJoinTableForeignKeyColumnName derives name from associated entity when no explicit config"() { @@ -53,6 +53,15 @@ class HibernateToManyPropertySpec extends HibernateGormDatastoreSpec { then: columnName == "custom_book_fk" } + + void "isAssociationColumnNullable returns false for ManyToMany"() { + when: + def studentEntity = mappingContext.getPersistentEntity(HTMPStudent.name) + def coursesProp = studentEntity.getPropertyByName("courses") + + then: + !coursesProp.isAssociationColumnNullable() + } } @Entity @@ -79,3 +88,19 @@ class HTMPAuthorCustom { books joinTable: [column: 'custom_book_fk'] } } + +@Entity +class HTMPStudent { + Long id + String name + Set<HTMPCourse> courses + static hasMany = [courses: HTMPCourse] +} + +@Entity +class HTMPCourse { + Long id + String title + Set<HTMPStudent> students + static hasMany = [students: HTMPStudent] +}
