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 f6612d5e003c3d77a868ee2831303e6d91a311be Author: Walter B Duque de Estrada <[email protected]> AuthorDate: Wed Jan 28 15:49:14 2026 -0600 progress cleaned up isIdentityProperty cleaned up isCompositeIdProperty --- .../core/HIBERNATE7-UPGRADE-PROGRESS.md | 6 +- .../orm/hibernate/cfg/CompositeIdentity.groovy | 2 +- .../orm/hibernate/cfg/GrailsDomainBinder.java | 303 +++------------------ .../cfg/GrailsHibernatePersistentEntity.java | 6 + .../orm/hibernate/cfg/HibernateIdentity.java | 7 + .../org/grails/orm/hibernate/cfg/Identity.groovy | 2 +- .../org/grails/orm/hibernate/cfg/Mapping.groovy | 11 +- .../cfg/domainbinding/CascadeBehaviorFetcher.java | 3 +- .../cfg/domainbinding/ComponentBinder.java | 60 ++++ .../cfg/domainbinding/ComponentPropertyBinder.java | 163 +++++++++++ .../cfg/domainbinding/CompositeIdBinder.java | 75 +++++ .../cfg/domainbinding/IdentityBinder.java | 76 ++++++ .../cfg/domainbinding/ManyToOneBinder.java | 10 +- .../cfg/domainbinding/OneToOneBinder.java | 65 +++++ .../domainbinding/PropertyFromValueCreator.java | 33 +++ .../cfg/domainbinding/SimpleIdBinder.java | 31 ++- .../cfg/domainbinding/SimpleValueBinder.java | 19 +- .../grails/orm/hibernate/cfg/MappingSpec.groovy | 29 +- .../cfg/domainbinding/ComponentBinderSpec.groovy | 65 +++++ .../ComponentPropertyBinderSpec.groovy | 230 ++++++++++++++++ .../cfg/domainbinding/CompositeIdBinderSpec.groovy | 95 +++++++ .../cfg/domainbinding/IdentityBinderSpec.groovy | 125 +++++++++ .../cfg/domainbinding/OneToOneBinderSpec.groovy | 121 ++++++++ .../PropertyFromValueCreatorSpec.groovy | 55 ++++ .../cfg/domainbinding/SimpleIdBinderSpec.groovy | 8 +- .../mapping/model/AbstractPersistentEntity.java | 6 +- .../mapping/model/PersistentProperty.java | 29 ++ 27 files changed, 1331 insertions(+), 304 deletions(-) diff --git a/grails-data-hibernate7/core/HIBERNATE7-UPGRADE-PROGRESS.md b/grails-data-hibernate7/core/HIBERNATE7-UPGRADE-PROGRESS.md index 57d3c34c8d..7dc62de505 100644 --- a/grails-data-hibernate7/core/HIBERNATE7-UPGRADE-PROGRESS.md +++ b/grails-data-hibernate7/core/HIBERNATE7-UPGRADE-PROGRESS.md @@ -4,11 +4,7 @@ This document summarizes the approaches taken, challenges encountered, and future steps for upgrading the GORM Hibernate implementation to Hibernate 7. ## Resolved Challenges -- **HibernateGormInstanceApiSpec Fix**: Replaced invalid `remove(flush: true)` calls with `delete(flush: true)`. GORM entities use `delete()` for deletion; `remove()` is specific to Data Services. -- **Isolated Broken IncrementGenerator**: Identified that `GrailsIncrementGenerator` is currently broken in Hibernate 7 due to physical table names not being available during initialization. Isolated the failure into `IncrementGeneratorSpec.groovy` to allow the rest of the suite to pass. -- **BasicValueIdCreator Cleanup**: Removed redundant manual `initialize()` calls on `IdentifierGenerator` during metadata collection. Hibernate manages this lifecycle later when the `SqlStringGenerationContext` is fully available. -- **TCK ProxyHandler Infrastructure**: Added `getProxyHandler()` to `GrailsDataTckManager` and `GrailsDataTckSpec` and implemented it across all TCK manager implementations (Hibernate 5/6/7, MongoDB, Simple). This allows TCK tests to use the `proxyHandler` property. -- **HibernateProxyHandler7Spec Fix**: Resolved a name collision where a `@Shared proxyHandler` field conflicted with the new `getProxyHandler()` method inherited from the base class. + ## Hibernate 7 Key Constraints & Best Practices - **Proxy Behavior Persistence**: A key lesson from `HibernateProxyHandler7Spec` is that once a class is loaded by Hibernate as a proxy, Hibernate will keep using that proxy instance within the session context even after it has been initialized. Unwrapping is necessary to get to the underlying implementation if needed. diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/CompositeIdentity.groovy b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/CompositeIdentity.groovy index 011ee728d5..e7f18e4f0f 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/CompositeIdentity.groovy +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/CompositeIdentity.groovy @@ -30,7 +30,7 @@ import org.grails.datastore.mapping.config.Property @AutoClone @Builder(builderStrategy = SimpleStrategy, prefix = '') @CompileStatic -class CompositeIdentity extends Property { +class CompositeIdentity extends Property implements HibernateIdentity { /** * The property names that make up the custom identity */ diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java index 6a904ef537..87570d1701 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java @@ -141,6 +141,9 @@ public class GrailsDomainBinder private final String dataSourceName; private final HibernateMappingContext hibernateMappingContext; private final ClassBinder classBinding; + private final EnumTypeBinder enumTypeBinder; + private final PropertyFromValueCreator propertyFromValueCreator; + private ComponentPropertyBinder componentPropertyBinder; private Closure defaultMapping; private PersistentEntityNamingStrategy namingStrategy; private MetadataBuildingContext metadataBuildingContext; @@ -155,13 +158,17 @@ public class GrailsDomainBinder public GrailsDomainBinder(String dataSourceName , String sessionFactoryName , HibernateMappingContext hibernateMappingContext - , ClassBinder classBinding) { + , ClassBinder classBinding + , EnumTypeBinder enumTypeBinder) { this.sessionFactoryName = sessionFactoryName; this.dataSourceName = dataSourceName; this.hibernateMappingContext = hibernateMappingContext; this.classBinding = classBinding; - mappingCacheHolder = MappingCacheHolder.getInstance(); + this.enumTypeBinder = enumTypeBinder; + this.propertyFromValueCreator = new PropertyFromValueCreator(); + this.mappingCacheHolder = MappingCacheHolder.getInstance(); this.collectionHolder = new CollectionHolder(this); + this.componentPropertyBinder = new ComponentPropertyBinder(null, null, mappingCacheHolder, collectionHolder, enumTypeBinder, propertyFromValueCreator); // pre-build mappings for (GrailsHibernatePersistentEntity persistentEntity : hibernateMappingContext.getHibernatePersistentEntities()) { mappingCacheHolder.cacheMapping(persistentEntity); @@ -179,6 +186,7 @@ public class GrailsDomainBinder , sessionFactoryName , hibernateMappingContext , new ClassBinder() + , new EnumTypeBinder() ); } @@ -211,6 +219,7 @@ public class GrailsDomainBinder metadataCollector , rootMappingDefaults ); + this.componentPropertyBinder = new ComponentPropertyBinder(metadataBuildingContext, getNamingStrategy(), getMappingCacheHolder(), getCollectionHolder(), enumTypeBinder, propertyFromValueCreator); hibernateMappingContext.getHibernatePersistentEntities().stream() .filter(persistentEntity -> persistentEntity.forGrailsDomainMapping(dataSourceName)) @@ -349,10 +358,7 @@ public class GrailsDomainBinder PersistentClass referenced = mappings.getEntityBinding(entityName); - Class<?> mappedClass = referenced.getMappedClass(); - Mapping m = Optional.ofNullable(MappingCacheHolder.getInstance().getMapping(mappedClass)).orElseThrow(); - - boolean compositeIdProperty = m.isCompositeIdProperty(property.getInverseSide()); + boolean compositeIdProperty = property.getInverseSide().isCompositeIdProperty(); if (!compositeIdProperty) { Backref prop = new Backref(); final PersistentEntity owner = property.getOwner(); @@ -1499,69 +1505,16 @@ public class GrailsDomainBinder - private void bindIdentity( + public void bindIdentity( GrailsHibernatePersistentEntity domainClass, RootClass root, InFlightMetadataCollector mappings, Mapping gormMapping, String sessionFactoryBeanName) { - PersistentProperty identifierProp = domainClass.getIdentity(); - if (gormMapping == null) { - if(identifierProp != null) { - SimpleIdBinder simpleIdBinder = new SimpleIdBinder(metadataBuildingContext,namingStrategy, getJdbcEnvironment(), domainClass, root); - simpleIdBinder.bindSimpleId(identifierProp, root, null); - - } - return; - } - - Object id = gormMapping.getIdentity(); - if (id instanceof CompositeIdentity) { - bindCompositeId(domainClass, root, (CompositeIdentity) id, mappings, sessionFactoryBeanName); - } else { - final Identity identity = (Identity) id; - String propertyName = identity.getName(); - if (propertyName != null && !propertyName.equals(domainClass.getName())) { - PersistentProperty namedIdentityProp = domainClass.getPropertyByName(propertyName); - if (namedIdentityProp == null) { - throw new MappingException("Mapping specifies an identifier property name that doesn't exist ["+propertyName+"]"); - } - if (!namedIdentityProp.equals(identifierProp)) { - identifierProp = namedIdentityProp; - } - } - SimpleIdBinder simpleIdBinder = new SimpleIdBinder(metadataBuildingContext,namingStrategy, getJdbcEnvironment(), domainClass, root); - simpleIdBinder.bindSimpleId(identifierProp, root, identity); - - } - } - - private void bindCompositeId(PersistentEntity domainClass, RootClass root, - CompositeIdentity compositeIdentity, InFlightMetadataCollector mappings, String sessionFactoryBeanName) { - GrailsHibernatePersistentEntity hibernatePersistentEntity = (GrailsHibernatePersistentEntity) domainClass; - Component id = new Component(metadataBuildingContext, root); - id.setNullValue("undefined"); - root.setIdentifier(id); - root.setIdentifierMapper(id); - root.setEmbeddedIdentifier(true); - id.setComponentClassName(domainClass.getName()); - id.setKey(true); - id.setEmbedded(true); - - String path = qualify(root.getEntityName(), "id"); - - id.setRoleName(path); - - final PersistentProperty[] composite = hibernatePersistentEntity.getCompositeIdentity(); - for (PersistentProperty property : composite) { - if (property == null) { - throw new MappingException("Property referenced in composite-id mapping of class [" + domainClass.getName() + - "] is not a valid property!"); - } - - bindComponentProperty(id, null, property, root, "", root.getTable(), mappings, sessionFactoryBeanName); - } + CompositeIdBinder compositeIdBinder = new CompositeIdBinder(metadataBuildingContext, componentPropertyBinder); + new IdentityBinder(metadataBuildingContext, getNamingStrategy(), getJdbcEnvironment(), compositeIdBinder) + .bindIdentity(domainClass, root, mappings, gormMapping, sessionFactoryBeanName); } /** @@ -1586,8 +1539,8 @@ public class GrailsDomainBinder final List<PersistentProperty> persistentProperties = domainClass.getPersistentProperties() .stream() .filter(persistentProperty -> persistentProperty.getMappedForm() != null) - .filter(persistentProperty -> !gormMapping.isCompositeIdProperty(persistentProperty)) - .filter(persistentProperty -> !gormMapping.isIdentityProperty(persistentProperty)) + .filter(persistentProperty -> !persistentProperty.isCompositeIdProperty()) + .filter(persistentProperty -> !persistentProperty.isIdentityProperty()) .filter(persistentProperty -> !persistentProperty.getName().equals(GormProperties.VERSION) ) .filter(persistentProperty -> !persistentProperty.isInherited()) .toList(); @@ -1633,7 +1586,8 @@ public class GrailsDomainBinder } else if (currentGrailsProp.getType().isEnum()) { value = new BasicValue(metadataBuildingContext, table); - bindEnumType(currentGrailsProp, (SimpleValue) value, EMPTY_PATH, sessionFactoryBeanName); + String columnName = new ColumnNameForPropertyAndPathFetcher(namingStrategy).getColumnNameForPropertyAndPath(currentGrailsProp, EMPTY_PATH, null); + enumTypeBinder.bindEnumType(currentGrailsProp, currentGrailsProp.getType(), (SimpleValue) value, columnName); } else if(currentGrailsProp instanceof Association) { Association association = (Association) currentGrailsProp; @@ -1656,12 +1610,12 @@ public class GrailsDomainBinder } else if (((Association) currentGrailsProp).canBindOneToOneWithSingleColumnAndForeignKey()) { value = new OneToOne(metadataBuildingContext, table, persistentClass); - bindOneToOne((org.grails.datastore.mapping.model.types.OneToOne) currentGrailsProp, (OneToOne) value, EMPTY_PATH, sessionFactoryBeanName); + new OneToOneBinder(namingStrategy).bindOneToOne((org.grails.datastore.mapping.model.types.OneToOne) currentGrailsProp, (OneToOne) value, EMPTY_PATH); } else { if (isHasOne && association.isBidirectional()) { value = new OneToOne(metadataBuildingContext, table, persistentClass); - bindOneToOne((org.grails.datastore.mapping.model.types.OneToOne) currentGrailsProp, (OneToOne) value, EMPTY_PATH, sessionFactoryBeanName); + new OneToOneBinder(namingStrategy).bindOneToOne((org.grails.datastore.mapping.model.types.OneToOne) currentGrailsProp, (OneToOne) value, EMPTY_PATH); } else { value = new ManyToOne(metadataBuildingContext, table); @@ -1685,7 +1639,7 @@ public class GrailsDomainBinder } if (value != null) { - Property property = createProperty(value, persistentClass, currentGrailsProp, mappings); + Property property = propertyFromValueCreator.createProperty(value, currentGrailsProp); persistentClass.addProperty(property); } } @@ -1693,8 +1647,8 @@ public class GrailsDomainBinder for (Embedded association : embedded) { Value value = new Component(metadataBuildingContext, persistentClass); - bindComponent((Component) value, association, true, mappings, sessionFactoryBeanName,mappingCacheHolder ); - Property property = createProperty(value, persistentClass, association, mappings); + componentPropertyBinder.bindComponent((Component) value, association, true, mappings, sessionFactoryBeanName); + Property property = propertyFromValueCreator.createProperty(value, association); persistentClass.addProperty(property); } new NaturalIdentifierBinder().bindNaturalIdentifier(gormMapping, persistentClass); @@ -1702,11 +1656,17 @@ public class GrailsDomainBinder - private void bindEnumType(PersistentProperty property, SimpleValue simpleValue, - String path, String sessionFactoryBeanName) { - Class<?> propertyType = property.getType(); - String columnName = new ColumnNameForPropertyAndPathFetcher(namingStrategy).getColumnNameForPropertyAndPath(property, path, null); - new EnumTypeBinder().bindEnumType(property, propertyType, simpleValue, columnName); + private void bindOneToMany(org.grails.datastore.mapping.model.types.OneToMany currentGrailsProp, OneToMany one, InFlightMetadataCollector mappings) { + one.setReferencedEntityName(currentGrailsProp.getAssociatedEntity().getName()); + one.setIgnoreNotFound(true); + } + + private String getIndexColumnName(PersistentProperty property, String sessionFactoryBeanName) { + PropertyConfig pc = new PersistentPropertyToPropertyConfig().toPropertyConfig(property); + if (pc.getIndexColumn() != null && pc.getIndexColumn().getColumn() != null) { + return pc.getIndexColumn().getColumn(); + } + return getNamingStrategy().resolveColumnName(property.getName()) + UNDERSCORE + IndexedCollection.DEFAULT_INDEX_COLUMN_NAME; } private Class<?> getUserType(PersistentProperty currentGrailsProp) { @@ -1732,188 +1692,7 @@ public class GrailsDomainBinder return userType; } - /** - * Binds a Hibernate component type using the given GrailsDomainClassProperty instance - * - * @param component The component to bind - * @param property The property - * @param isNullable Whether it is nullable or not - * @param mappings The Hibernate Mappings object - * @param sessionFactoryBeanName the session factory bean name - * @param mappingCacheHolder - */ - private void bindComponent(Component component, Embedded property, - boolean isNullable, InFlightMetadataCollector mappings, String sessionFactoryBeanName, MappingCacheHolder mappingCacheHolder) { - Class<?> type = property.getType(); - String role = qualify(type.getName(), property.getName()); - component.setRoleName(role); - component.setComponentClassName(type.getName()); - - GrailsHibernatePersistentEntity domainClass = (GrailsHibernatePersistentEntity) property.getAssociatedEntity(); - mappingCacheHolder.cacheMapping(domainClass); - final List<PersistentProperty> properties = domainClass.getPersistentProperties(); - Table table = component.getOwner().getTable(); - PersistentClass persistentClass = component.getOwner(); - String path = property.getName(); - Class<?> propertyType = property.getOwner().getJavaClass(); - - for (PersistentProperty currentGrailsProp : properties) { - if (currentGrailsProp.equals(domainClass.getIdentity())) continue; - if (currentGrailsProp.getName().equals(GormProperties.VERSION)) continue; - - if (currentGrailsProp.getType().equals(propertyType)) { - component.setParentProperty(currentGrailsProp.getName()); - continue; - } - - bindComponentProperty(component, property, currentGrailsProp, persistentClass, path, - table, mappings, sessionFactoryBeanName); - } - } - - private void bindComponentProperty(Component component, PersistentProperty componentProperty, - PersistentProperty currentGrailsProp, PersistentClass persistentClass, - String path, Table table, InFlightMetadataCollector mappings, String sessionFactoryBeanName) { - Value value; - // see if it's a collection type - CollectionType collectionType = collectionHolder.get(currentGrailsProp.getType()); - if (collectionType != null) { - // create collection - Collection collection = collectionType.create((ToMany) currentGrailsProp, persistentClass, - path, mappings, sessionFactoryBeanName); - mappings.addCollectionBinding(collection); - value = collection; - } - // work out what type of relationship it is and bind value - else if (currentGrailsProp instanceof org.grails.datastore.mapping.model.types.ManyToOne) { - if (LOG.isDebugEnabled()) - LOG.debug("[GrailsDomainBinder] Binding property [" + currentGrailsProp.getName() + "] as ManyToOne"); - - value = new ManyToOne(metadataBuildingContext, table); - new ManyToOneBinder(namingStrategy).bindManyToOne((Association) currentGrailsProp, (ManyToOne) value, path); - } else if (currentGrailsProp instanceof org.grails.datastore.mapping.model.types.OneToOne) { - if (LOG.isDebugEnabled()) - LOG.debug("[GrailsDomainBinder] Binding property [" + currentGrailsProp.getName() + "] as OneToOne"); - - if (((Association) currentGrailsProp).canBindOneToOneWithSingleColumnAndForeignKey()) { - value = new OneToOne(metadataBuildingContext, table, persistentClass); - bindOneToOne((org.grails.datastore.mapping.model.types.OneToOne) currentGrailsProp, (OneToOne) value, path, sessionFactoryBeanName); - } - else { - value = new ManyToOne(metadataBuildingContext, table); - new ManyToOneBinder(namingStrategy).bindManyToOne((Association) currentGrailsProp, (ManyToOne) value, path); - } - } - else if (currentGrailsProp instanceof Embedded) { - value = new Component(metadataBuildingContext, persistentClass); - bindComponent((Component) value, (Embedded) currentGrailsProp, true, mappings, sessionFactoryBeanName,mappingCacheHolder ); - } - else { - if (LOG.isDebugEnabled()) - LOG.debug("[GrailsDomainBinder] Binding property [" + currentGrailsProp.getName() + "] as SimpleValue"); - - value = new BasicValue(metadataBuildingContext, table); - if (currentGrailsProp.getType().isEnum()) { - bindEnumType(currentGrailsProp, (SimpleValue) value, path, sessionFactoryBeanName); - } - else { - // set type - new SimpleValueBinder(namingStrategy).bindSimpleValue(currentGrailsProp, componentProperty, (SimpleValue) value, path); - } - } - - if (value != null) { - Property persistentProperty = createProperty(value, persistentClass, currentGrailsProp, mappings); - component.addProperty(persistentProperty); - if (isComponentPropertyNullable(componentProperty)) { - final Iterator<?> columnIterator = value.getColumns().iterator(); - while (columnIterator.hasNext()) { - Column c = (Column) columnIterator.next(); - c.setNullable(true); - } - } - } - } - - private boolean isComponentPropertyNullable(PersistentProperty componentProperty) { - if (componentProperty == null) return false; - final PersistentEntity domainClass = componentProperty.getOwner(); - Mapping mapping = null; - if (domainClass != null) { - mapping = ((GrailsHibernatePersistentEntity) domainClass).getMappedForm(); - } - return !domainClass.isRoot() && (mapping == null || mapping.isTablePerHierarchy()) || componentProperty.isNullable(); - } - - /* - * Creates a persistent class property based on the GrailDomainClassProperty instance. - */ - private Property createProperty(Value value, PersistentClass persistentClass, PersistentProperty grailsProperty, InFlightMetadataCollector mappings) { - // set type - value.setTypeUsingReflection(grailsProperty.getOwner().getJavaClass().getName(), grailsProperty.getName()); - - if (value.getTable() != null) { - value.createForeignKey(); - } - - Property prop = new Property(); - prop.setValue(value); - new PropertyBinder().bindProperty(grailsProperty, prop); - return prop; - } - - private void bindOneToMany(org.grails.datastore.mapping.model.types.OneToMany currentGrailsProp, OneToMany one, InFlightMetadataCollector mappings) { - one.setReferencedEntityName(currentGrailsProp.getAssociatedEntity().getName()); - one.setIgnoreNotFound(true); - } - - // each property may consist of one or many columns (due to composite ids) so in order to get the - // number of columns required for a column key we have to perform the calculation here - - private void bindOneToOne(final org.grails.datastore.mapping.model.types.OneToOne property, OneToOne oneToOne, - String path, String sessionFactoryBeanName) { - PropertyConfig config = new PersistentPropertyToPropertyConfig().toPropertyConfig(property); - final Association otherSide = property.getInverseSide(); - - final boolean hasOne = otherSide.isHasOne(); - oneToOne.setConstrained(hasOne); - oneToOne.setForeignKeyType(oneToOne.isConstrained() ? - ForeignKeyDirection.FROM_PARENT : - ForeignKeyDirection.TO_PARENT); - oneToOne.setAlternateUniqueKey(true); - - if (config != null && config.getFetchMode() != null) { - oneToOne.setFetchMode(config.getFetchMode()); - } - else { - oneToOne.setFetchMode(FetchMode.DEFAULT); - } - - oneToOne.setReferencedEntityName(otherSide.getOwner().getName()); - oneToOne.setPropertyName(property.getName()); - oneToOne.setReferenceToPrimaryKey(false); - - //no-op, for subclasses to extend - - if (hasOne) { - //TODO NOT TESTED - // set type - new SimpleValueBinder(namingStrategy).bindSimpleValue(property, null, oneToOne, path); - } - else { - oneToOne.setReferencedPropertyName(otherSide.getName()); - } - } - - private String getIndexColumnName(PersistentProperty property, String sessionFactoryBeanName) { - PropertyConfig pc = new PersistentPropertyToPropertyConfig().toPropertyConfig(property); - if (pc.getIndexColumn() != null && pc.getIndexColumn().getColumn() != null) { - return pc.getIndexColumn().getColumn(); - } - return getNamingStrategy().resolveColumnName(property.getName()) + UNDERSCORE + IndexedCollection.DEFAULT_INDEX_COLUMN_NAME; - } - - private String getIndexColumnType(PersistentProperty property, String defaultType) { + private String getIndexColumnType(PersistentProperty property, String defaultType) { PropertyConfig pc = new PersistentPropertyToPropertyConfig().toPropertyConfig(property); @@ -1959,6 +1738,14 @@ public class GrailsDomainBinder return metadataBuildingContext; } + public MappingCacheHolder getMappingCacheHolder() { + return mappingCacheHolder; + } + + public CollectionHolder getCollectionHolder() { + return collectionHolder; + } + @Override public String getContributorName() { return "GORM"; diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsHibernatePersistentEntity.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsHibernatePersistentEntity.java index 05a326f2dc..e823f31dc5 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsHibernatePersistentEntity.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsHibernatePersistentEntity.java @@ -31,4 +31,10 @@ public interface GrailsHibernatePersistentEntity extends PersistentEntity { .toList(); } + default boolean isComponentPropertyNullable(PersistentProperty embeddedProperty) { + if (embeddedProperty == null) return false; + final Mapping mapping = getMappedForm(); + return !isRoot() && (mapping == null || mapping.isTablePerHierarchy()) || embeddedProperty.isNullable(); + } + } diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/HibernateIdentity.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/HibernateIdentity.java new file mode 100644 index 0000000000..e94fc007e5 --- /dev/null +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/HibernateIdentity.java @@ -0,0 +1,7 @@ +package org.grails.orm.hibernate.cfg; + +/** + * A marker interface for single and composite identity configurations in GORM for Hibernate. + */ +public interface HibernateIdentity { +} 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 93483033a0..03a7563022 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 @@ -31,7 +31,7 @@ import org.springframework.validation.DataBinder */ @CompileStatic @Builder(builderStrategy = SimpleStrategy, prefix = '') -class Identity extends Property { +class Identity extends Property implements HibernateIdentity { /** * The generator to use */ diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/Mapping.groovy b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/Mapping.groovy index f8171cb0aa..93b8f4c6a4 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/Mapping.groovy +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/Mapping.groovy @@ -97,16 +97,7 @@ class Mapping extends Entity<PropertyConfig> { /** * The identity definition */ - Property identity = new Identity() - - boolean isCompositeIdProperty(PersistentProperty property) { - return (identity instanceof CompositeIdentity) && (property.name in identity.propertyNames) - } - - boolean isIdentityProperty(PersistentProperty property) { - def identityMapping = getIdentity() - return (identityMapping instanceof Identity) && (identityMapping.name == property.name) - } + HibernateIdentity identity = new Identity() /** diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/CascadeBehaviorFetcher.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/CascadeBehaviorFetcher.java index ae79098f73..2f4b02fc4d 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/CascadeBehaviorFetcher.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/CascadeBehaviorFetcher.java @@ -66,8 +66,7 @@ public class CascadeBehaviorFetcher { if ( association.isCorrectlyOwned() && !association.isCircular()) { return ALL; } - else if (getOwnersWrappedForm(association) - .isCompositeIdProperty(association)) { + else if (association.isCompositeIdProperty()) { return ALL; } else { return NONE; diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/ComponentBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/ComponentBinder.java new file mode 100644 index 0000000000..2ec1425445 --- /dev/null +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/ComponentBinder.java @@ -0,0 +1,60 @@ +package org.grails.orm.hibernate.cfg.domainbinding; + +import java.util.List; + +import org.hibernate.boot.spi.InFlightMetadataCollector; +import org.hibernate.mapping.Component; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Table; + +import org.grails.datastore.mapping.model.PersistentProperty; +import org.grails.datastore.mapping.model.config.GormProperties; +import org.grails.datastore.mapping.model.types.Embedded; +import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity; +import org.grails.orm.hibernate.cfg.GrailsHibernateUtil; +import org.grails.orm.hibernate.cfg.MappingCacheHolder; + +public class ComponentBinder { + + private final MappingCacheHolder mappingCacheHolder; + private final ComponentPropertyBinder componentPropertyBinder; + + public ComponentBinder(MappingCacheHolder mappingCacheHolder, ComponentPropertyBinder componentPropertyBinder) { + this.mappingCacheHolder = mappingCacheHolder; + this.componentPropertyBinder = componentPropertyBinder; + } + + protected ComponentBinder() { + this.mappingCacheHolder = null; + this.componentPropertyBinder = null; + } + + public void bindComponent(Component component, Embedded property, + boolean isNullable, InFlightMetadataCollector mappings, String sessionFactoryBeanName) { + Class<?> type = property.getType(); + String role = GrailsHibernateUtil.qualify(type.getName(), property.getName()); + component.setRoleName(role); + component.setComponentClassName(type.getName()); + + GrailsHibernatePersistentEntity domainClass = (GrailsHibernatePersistentEntity) property.getAssociatedEntity(); + mappingCacheHolder.cacheMapping(domainClass); + final List<PersistentProperty> properties = domainClass.getPersistentProperties(); + Table table = component.getOwner().getTable(); + PersistentClass persistentClass = component.getOwner(); + String path = property.getName(); + Class<?> propertyType = property.getOwner().getJavaClass(); + + for (PersistentProperty currentGrailsProp : properties) { + if (currentGrailsProp.equals(domainClass.getIdentity())) continue; + if (currentGrailsProp.getName().equals(GormProperties.VERSION)) continue; + + if (currentGrailsProp.getType().equals(propertyType)) { + component.setParentProperty(currentGrailsProp.getName()); + continue; + } + + componentPropertyBinder.bindComponentProperty(component, property, currentGrailsProp, persistentClass, path, + table, mappings, sessionFactoryBeanName); + } + } +} diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/ComponentPropertyBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/ComponentPropertyBinder.java new file mode 100644 index 0000000000..6fb1bbf121 --- /dev/null +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/ComponentPropertyBinder.java @@ -0,0 +1,163 @@ +package org.grails.orm.hibernate.cfg.domainbinding; + +import java.util.Iterator; +import java.util.List; + +import org.hibernate.FetchMode; +import org.hibernate.boot.spi.InFlightMetadataCollector; +import org.hibernate.boot.spi.MetadataBuildingContext; +import org.hibernate.mapping.BasicValue; +import org.hibernate.mapping.Collection; +import org.hibernate.mapping.Column; +import org.hibernate.mapping.Component; +import org.hibernate.mapping.ManyToOne; +import org.hibernate.mapping.OneToOne; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; +import org.hibernate.mapping.SimpleValue; +import org.hibernate.mapping.Table; +import org.hibernate.mapping.Value; +import org.hibernate.type.ForeignKeyDirection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +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.datastore.mapping.model.types.Association; +import org.grails.datastore.mapping.model.types.Embedded; +import org.grails.datastore.mapping.model.types.ToMany; +import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity; +import org.grails.orm.hibernate.cfg.GrailsHibernateUtil; +import org.grails.orm.hibernate.cfg.Mapping; +import org.grails.orm.hibernate.cfg.MappingCacheHolder; +import org.grails.orm.hibernate.cfg.PersistentEntityNamingStrategy; +import org.grails.orm.hibernate.cfg.PropertyConfig; +import org.grails.orm.hibernate.cfg.domainbinding.collectionType.CollectionHolder; +import org.grails.orm.hibernate.cfg.domainbinding.collectionType.CollectionType; + +import static org.grails.orm.hibernate.cfg.GrailsDomainBinder.EMPTY_PATH; + +public class ComponentPropertyBinder { + + private static final Logger LOG = LoggerFactory.getLogger(ComponentPropertyBinder.class); + + private final MetadataBuildingContext metadataBuildingContext; + private final PersistentEntityNamingStrategy namingStrategy; + private final MappingCacheHolder mappingCacheHolder; + private final CollectionHolder collectionHolder; + private final EnumTypeBinder enumTypeBinder; + private final PropertyFromValueCreator propertyFromValueCreator; + private final ComponentBinder componentBinder; + private final PersistentPropertyToPropertyConfig persistentPropertyToPropertyConfig; + + public ComponentPropertyBinder(MetadataBuildingContext metadataBuildingContext, + PersistentEntityNamingStrategy namingStrategy, + MappingCacheHolder mappingCacheHolder, + CollectionHolder collectionHolder, + EnumTypeBinder enumTypeBinder, + PropertyFromValueCreator propertyFromValueCreator) { + this(metadataBuildingContext, namingStrategy, mappingCacheHolder, collectionHolder, + enumTypeBinder, propertyFromValueCreator, null, new PersistentPropertyToPropertyConfig()); + } + + protected ComponentPropertyBinder(MetadataBuildingContext metadataBuildingContext, + PersistentEntityNamingStrategy namingStrategy, + MappingCacheHolder mappingCacheHolder, + CollectionHolder collectionHolder, + EnumTypeBinder enumTypeBinder, + PropertyFromValueCreator propertyFromValueCreator, + ComponentBinder componentBinder, + PersistentPropertyToPropertyConfig persistentPropertyToPropertyConfig) { + this.metadataBuildingContext = metadataBuildingContext; + this.namingStrategy = namingStrategy; + this.mappingCacheHolder = mappingCacheHolder; + this.collectionHolder = collectionHolder; + this.enumTypeBinder = enumTypeBinder; + this.propertyFromValueCreator = propertyFromValueCreator; + this.componentBinder = componentBinder != null ? componentBinder : new ComponentBinder(mappingCacheHolder, this); + this.persistentPropertyToPropertyConfig = persistentPropertyToPropertyConfig; + } + + protected ComponentPropertyBinder() { + this.metadataBuildingContext = null; + this.namingStrategy = null; + this.mappingCacheHolder = null; + this.collectionHolder = null; + this.enumTypeBinder = null; + this.propertyFromValueCreator = null; + this.componentBinder = null; + this.persistentPropertyToPropertyConfig = null; + } + + public void bindComponentProperty(Component component, PersistentProperty componentProperty, + PersistentProperty currentGrailsProp, PersistentClass persistentClass, + String path, Table table, InFlightMetadataCollector mappings, String sessionFactoryBeanName) { + Value value; + // see if it's a collection type + CollectionType collectionType = collectionHolder.get(currentGrailsProp.getType()); + if (collectionType != null) { + // create collection + Collection collection = collectionType.create((ToMany) currentGrailsProp, persistentClass, + path, mappings, sessionFactoryBeanName); + mappings.addCollectionBinding(collection); + value = collection; + } + // work out what type of relationship it is and bind value + else if (currentGrailsProp instanceof org.grails.datastore.mapping.model.types.ManyToOne) { + if (LOG.isDebugEnabled()) + LOG.debug("[GrailsDomainBinder] Binding property [" + currentGrailsProp.getName() + "] as ManyToOne"); + + value = new ManyToOne(metadataBuildingContext, table); + new ManyToOneBinder(namingStrategy, persistentPropertyToPropertyConfig).bindManyToOne((Association) currentGrailsProp, (ManyToOne) value, path); + } else if (currentGrailsProp instanceof org.grails.datastore.mapping.model.types.OneToOne) { + if (LOG.isDebugEnabled()) + LOG.debug("[GrailsDomainBinder] Binding property [" + currentGrailsProp.getName() + "] as OneToOne"); + + if (((Association) currentGrailsProp).canBindOneToOneWithSingleColumnAndForeignKey()) { + value = new OneToOne(metadataBuildingContext, table, persistentClass); + new OneToOneBinder(namingStrategy, persistentPropertyToPropertyConfig).bindOneToOne((org.grails.datastore.mapping.model.types.OneToOne) currentGrailsProp, (OneToOne) value, path); + } + else { + value = new ManyToOne(metadataBuildingContext, table); + new ManyToOneBinder(namingStrategy, persistentPropertyToPropertyConfig).bindManyToOne((Association) currentGrailsProp, (ManyToOne) value, path); + } + } + else if (currentGrailsProp instanceof Embedded) { + value = new Component(metadataBuildingContext, persistentClass); + componentBinder.bindComponent((Component) value, (Embedded) currentGrailsProp, true, mappings, sessionFactoryBeanName); + } + else { + if (LOG.isDebugEnabled()) + LOG.debug("[GrailsDomainBinder] Binding property [" + currentGrailsProp.getName() + "] as SimpleValue"); + + value = new BasicValue(metadataBuildingContext, table); + if (currentGrailsProp.getType().isEnum()) { + String columnName = new ColumnNameForPropertyAndPathFetcher(namingStrategy, persistentPropertyToPropertyConfig, + new DefaultColumnNameFetcher(namingStrategy), new BackticksRemover()).getColumnNameForPropertyAndPath(currentGrailsProp, path, null); + enumTypeBinder.bindEnumType(currentGrailsProp, currentGrailsProp.getType(), (SimpleValue) value, columnName); + } + else { + // set type + new SimpleValueBinder(namingStrategy, persistentPropertyToPropertyConfig).bindSimpleValue(currentGrailsProp, componentProperty, (SimpleValue) value, path); + } + } + + if (value != null) { + Property persistentProperty = propertyFromValueCreator.createProperty(value, currentGrailsProp); + component.addProperty(persistentProperty); + if (componentProperty != null && componentProperty.getOwner() instanceof GrailsHibernatePersistentEntity ghpe && ghpe.isComponentPropertyNullable(componentProperty)) { + final Iterator<?> columnIterator = value.getColumns().iterator(); + while (columnIterator.hasNext()) { + Column c = (Column) columnIterator.next(); + c.setNullable(true); + } + } + } + } + + public void bindComponent(Component component, Embedded property, + boolean isNullable, InFlightMetadataCollector mappings, String sessionFactoryBeanName) { + componentBinder.bindComponent(component, property, isNullable, mappings, sessionFactoryBeanName); + } +} diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/CompositeIdBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/CompositeIdBinder.java new file mode 100644 index 0000000000..d6ca8d9bee --- /dev/null +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/CompositeIdBinder.java @@ -0,0 +1,75 @@ +package org.grails.orm.hibernate.cfg.domainbinding; + +import org.hibernate.MappingException; +import org.hibernate.boot.spi.InFlightMetadataCollector; +import org.hibernate.boot.spi.MetadataBuildingContext; +import org.hibernate.mapping.Component; +import org.hibernate.mapping.RootClass; + +import org.grails.datastore.mapping.model.PersistentEntity; +import org.grails.datastore.mapping.model.PersistentProperty; +import org.grails.orm.hibernate.cfg.CompositeIdentity; +import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity; +import org.grails.orm.hibernate.cfg.GrailsHibernateUtil; + +public class CompositeIdBinder { + + private final MetadataBuildingContext metadataBuildingContext; + private final ComponentPropertyBinder componentPropertyBinder; + + public CompositeIdBinder(MetadataBuildingContext metadataBuildingContext, ComponentPropertyBinder componentPropertyBinder) { + this.metadataBuildingContext = metadataBuildingContext; + this.componentPropertyBinder = componentPropertyBinder; + } + + protected CompositeIdBinder() { + this.metadataBuildingContext = null; + this.componentPropertyBinder = null; + } + + public void bindCompositeId(PersistentEntity domainClass, RootClass root, + CompositeIdentity compositeIdentity, InFlightMetadataCollector mappings, String sessionFactoryBeanName) { + bindCompositeId(domainClass, (GrailsHibernatePersistentEntity) domainClass, root, compositeIdentity, mappings, sessionFactoryBeanName); + } + + public void bindCompositeId(PersistentEntity domainClass, GrailsHibernatePersistentEntity hibernatePersistentEntity, RootClass root, + CompositeIdentity compositeIdentity, InFlightMetadataCollector mappings, String sessionFactoryBeanName) { + Component id = new Component(metadataBuildingContext, root); + id.setNullValue("undefined"); + root.setIdentifier(id); + root.setIdentifierMapper(id); + root.setEmbeddedIdentifier(true); + id.setComponentClassName(domainClass.getName()); + id.setKey(true); + id.setEmbedded(true); + + String path = GrailsHibernateUtil.qualify(root.getEntityName(), "id"); + + id.setRoleName(path); + + PersistentProperty[] composite; + if (compositeIdentity != null && compositeIdentity.getPropertyNames() != null) { + String[] propertyNames = compositeIdentity.getPropertyNames(); + composite = new PersistentProperty[propertyNames.length]; + for (int i = 0; i < propertyNames.length; i++) { + composite[i] = domainClass.getPropertyByName(propertyNames[i]); + } + } else { + composite = hibernatePersistentEntity.getCompositeIdentity(); + } + + if (composite == null) { + throw new MappingException("No composite identifier properties found for class [" + domainClass.getName() + "]"); + } + + PersistentProperty identifierProp = hibernatePersistentEntity.getIdentity(); + for (PersistentProperty property : composite) { + if (property == null) { + throw new MappingException("Property referenced in composite-id mapping of class [" + domainClass.getName() + + "] is not a valid property!"); + } + + componentPropertyBinder.bindComponentProperty(id, identifierProp, property, root, "", root.getTable(), mappings, sessionFactoryBeanName); + } + } +} diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/IdentityBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/IdentityBinder.java new file mode 100644 index 0000000000..cff53074ba --- /dev/null +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/IdentityBinder.java @@ -0,0 +1,76 @@ +package org.grails.orm.hibernate.cfg.domainbinding; + +import org.hibernate.MappingException; +import org.hibernate.boot.spi.InFlightMetadataCollector; +import org.hibernate.boot.spi.MetadataBuildingContext; +import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; +import org.hibernate.mapping.RootClass; + +import org.grails.datastore.mapping.model.PersistentProperty; +import org.grails.orm.hibernate.cfg.CompositeIdentity; +import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity; +import org.grails.orm.hibernate.cfg.HibernateIdentity; +import org.grails.orm.hibernate.cfg.Identity; +import org.grails.orm.hibernate.cfg.Mapping; +import org.grails.orm.hibernate.cfg.PersistentEntityNamingStrategy; + +public class IdentityBinder { + + private final SimpleIdBinder simpleIdBinder; + private final CompositeIdBinder compositeIdBinder; + + public IdentityBinder(MetadataBuildingContext metadataBuildingContext, + PersistentEntityNamingStrategy namingStrategy, + JdbcEnvironment jdbcEnvironment, + CompositeIdBinder compositeIdBinder) { + this(new SimpleIdBinder(metadataBuildingContext, namingStrategy, jdbcEnvironment), compositeIdBinder); + } + + public IdentityBinder(SimpleIdBinder simpleIdBinder, CompositeIdBinder compositeIdBinder) { + this.simpleIdBinder = simpleIdBinder; + this.compositeIdBinder = compositeIdBinder; + } + + protected IdentityBinder() { + this.simpleIdBinder = null; + this.compositeIdBinder = null; + } + + public void bindIdentity( + GrailsHibernatePersistentEntity domainClass, + RootClass root, + InFlightMetadataCollector mappings, + Mapping gormMapping, + String sessionFactoryBeanName) { + + if (gormMapping != null) { + HibernateIdentity id = gormMapping.getIdentity(); + if (id instanceof CompositeIdentity) { + compositeIdBinder.bindCompositeId(domainClass, root, (CompositeIdentity) id, mappings, sessionFactoryBeanName); + } else { + final Identity identity = (Identity) id; + PersistentProperty identifierProp = domainClass.getIdentity(); + String propertyName = identity.getName(); + if (propertyName != null && !propertyName.equals(domainClass.getName())) { + PersistentProperty namedIdentityProp = domainClass.getPropertyByName(propertyName); + if (namedIdentityProp == null) { + throw new MappingException("Mapping specifies an identifier property name that doesn't exist [" + propertyName + "]"); + } + if (!namedIdentityProp.equals(identifierProp)) { + identifierProp = namedIdentityProp; + } + } + simpleIdBinder.bindSimpleId(identifierProp, root, identity); + } + } else { + if (domainClass.getCompositeIdentity() != null) { + compositeIdBinder.bindCompositeId(domainClass, root, null, mappings, sessionFactoryBeanName); + } else { + PersistentProperty identifierProp = domainClass.getIdentity(); + if (identifierProp != null) { + simpleIdBinder.bindSimpleId(identifierProp, root, null); + } + } + } + } +} \ No newline at end of file diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/ManyToOneBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/ManyToOneBinder.java index fda5740928..e91ca28fff 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/ManyToOneBinder.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/ManyToOneBinder.java @@ -28,10 +28,14 @@ public class ManyToOneBinder { private final SimpleValueColumnFetcher simpleValueColumnFetcher; public ManyToOneBinder(PersistentEntityNamingStrategy namingStrategy) { + this(namingStrategy, new PersistentPropertyToPropertyConfig()); + } + + protected ManyToOneBinder(PersistentEntityNamingStrategy namingStrategy, PersistentPropertyToPropertyConfig persistentPropertyToPropertyConfig) { this.namingStrategy = namingStrategy; - this.simpleValueBinder = new SimpleValueBinder(namingStrategy); - this.persistentPropertyToPropertyConfig = new PersistentPropertyToPropertyConfig(); - this.manyToOneValuesBinder = new ManyToOneValuesBinder(); + this.persistentPropertyToPropertyConfig = persistentPropertyToPropertyConfig; + this.simpleValueBinder = new SimpleValueBinder(namingStrategy, persistentPropertyToPropertyConfig); + this.manyToOneValuesBinder = new ManyToOneValuesBinder(persistentPropertyToPropertyConfig); this.compositeIdentifierToManyToOneBinder = new CompositeIdentifierToManyToOneBinder(namingStrategy); this.simpleValueColumnFetcher = new SimpleValueColumnFetcher(); } diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/OneToOneBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/OneToOneBinder.java new file mode 100644 index 0000000000..a265256c0c --- /dev/null +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/OneToOneBinder.java @@ -0,0 +1,65 @@ +package org.grails.orm.hibernate.cfg.domainbinding; + +import org.hibernate.FetchMode; +import org.hibernate.mapping.OneToOne; +import org.hibernate.type.ForeignKeyDirection; + +import org.grails.datastore.mapping.model.types.Association; +import org.grails.orm.hibernate.cfg.PersistentEntityNamingStrategy; +import org.grails.orm.hibernate.cfg.PropertyConfig; + +public class OneToOneBinder { + + private final PersistentEntityNamingStrategy namingStrategy; + private final PersistentPropertyToPropertyConfig persistentPropertyToPropertyConfig; + private final SimpleValueBinder simpleValueBinder; + + public OneToOneBinder(PersistentEntityNamingStrategy namingStrategy) { + this(namingStrategy, new PersistentPropertyToPropertyConfig()); + } + + protected OneToOneBinder(PersistentEntityNamingStrategy namingStrategy, PersistentPropertyToPropertyConfig persistentPropertyToPropertyConfig) { + this.namingStrategy = namingStrategy; + this.persistentPropertyToPropertyConfig = persistentPropertyToPropertyConfig; + this.simpleValueBinder = new SimpleValueBinder(namingStrategy, persistentPropertyToPropertyConfig); + } + + protected OneToOneBinder(PersistentEntityNamingStrategy namingStrategy, + PersistentPropertyToPropertyConfig persistentPropertyToPropertyConfig, + SimpleValueBinder simpleValueBinder) { + this.namingStrategy = namingStrategy; + this.persistentPropertyToPropertyConfig = persistentPropertyToPropertyConfig; + this.simpleValueBinder = simpleValueBinder; + } + + public void bindOneToOne(final org.grails.datastore.mapping.model.types.OneToOne property, OneToOne oneToOne, + String path) { + PropertyConfig config = persistentPropertyToPropertyConfig.toPropertyConfig(property); + final Association otherSide = property.getInverseSide(); + + final boolean hasOne = otherSide.isHasOne(); + oneToOne.setConstrained(hasOne); + oneToOne.setForeignKeyType(oneToOne.isConstrained() ? + ForeignKeyDirection.FROM_PARENT : + ForeignKeyDirection.TO_PARENT); + oneToOne.setAlternateUniqueKey(true); + + if (config != null && config.getFetchMode() != null) { + oneToOne.setFetchMode(config.getFetchMode()); + } + else { + oneToOne.setFetchMode(FetchMode.DEFAULT); + } + + oneToOne.setReferencedEntityName(otherSide.getOwner().getName()); + oneToOne.setPropertyName(property.getName()); + oneToOne.setReferenceToPrimaryKey(false); + + if (hasOne) { + simpleValueBinder.bindSimpleValue(property, null, oneToOne, path); + } + else { + oneToOne.setReferencedPropertyName(otherSide.getName()); + } + } +} \ No newline at end of file diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/PropertyFromValueCreator.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/PropertyFromValueCreator.java new file mode 100644 index 0000000000..0a9714f623 --- /dev/null +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/PropertyFromValueCreator.java @@ -0,0 +1,33 @@ +package org.grails.orm.hibernate.cfg.domainbinding; + +import org.hibernate.mapping.Property; +import org.hibernate.mapping.Value; + +import org.grails.datastore.mapping.model.PersistentProperty; + +public class PropertyFromValueCreator { + + private final PropertyBinder propertyBinder; + + public PropertyFromValueCreator() { + this.propertyBinder = new PropertyBinder(); + } + + protected PropertyFromValueCreator(PropertyBinder propertyBinder) { + this.propertyBinder = propertyBinder; + } + + public Property createProperty(Value value, PersistentProperty grailsProperty) { + // set type + value.setTypeUsingReflection(grailsProperty.getOwnerClassName(), grailsProperty.getName()); + + if (value.getTable() != null) { + value.createForeignKey(); + } + + Property prop = new Property(); + prop.setValue(value); + propertyBinder.bindProperty(grailsProperty, prop); + return prop; + } +} \ No newline at end of file diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/SimpleIdBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/SimpleIdBinder.java index 09512b0a0a..8baa96854b 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/SimpleIdBinder.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/SimpleIdBinder.java @@ -18,33 +18,46 @@ import static org.grails.orm.hibernate.cfg.GrailsDomainBinder.EMPTY_PATH; public class SimpleIdBinder { - private final BasicValueIdCreator basicValueIdCreator; + private final MetadataBuildingContext metadataBuildingContext; + private final JdbcEnvironment jdbcEnvironment; private final SimpleValueBinder simpleValueBinder; private final PropertyBinder propertyBinder; + private final BasicValueIdCreator basicValueIdCreator; + + public SimpleIdBinder(MetadataBuildingContext metadataBuildingContext, PersistentEntityNamingStrategy namingStrategy, JdbcEnvironment jdbcEnvironment) { + this(metadataBuildingContext, jdbcEnvironment, new SimpleValueBinder(namingStrategy), new PropertyBinder(), null); + } - public SimpleIdBinder(MetadataBuildingContext metadataBuildingContext, PersistentEntityNamingStrategy namingStrategy, JdbcEnvironment jdbcEnvironment, GrailsHibernatePersistentEntity domainClass, RootClass entity) { - this.basicValueIdCreator = new BasicValueIdCreator(metadataBuildingContext, jdbcEnvironment, domainClass, entity); - this.simpleValueBinder =new SimpleValueBinder(namingStrategy); - this.propertyBinder = new PropertyBinder(); + public SimpleIdBinder(BasicValueIdCreator basicValueIdCreator, SimpleValueBinder simpleValueBinder, PropertyBinder propertyBinder) { + this.metadataBuildingContext = null; + this.jdbcEnvironment = null; + this.simpleValueBinder = simpleValueBinder; + this.propertyBinder = propertyBinder; + this.basicValueIdCreator = basicValueIdCreator; } - protected SimpleIdBinder(BasicValueIdCreator basicValueIdCreate, SimpleValueBinder simpleValueBinder, PropertyBinder propertyBinder) { - this.basicValueIdCreator = basicValueIdCreate; + protected SimpleIdBinder(MetadataBuildingContext metadataBuildingContext, JdbcEnvironment jdbcEnvironment, SimpleValueBinder simpleValueBinder, PropertyBinder propertyBinder, BasicValueIdCreator basicValueIdCreator) { + this.metadataBuildingContext = metadataBuildingContext; + this.jdbcEnvironment = jdbcEnvironment; this.simpleValueBinder = simpleValueBinder; this.propertyBinder = propertyBinder; + this.basicValueIdCreator = basicValueIdCreator; } public void bindSimpleId(PersistentProperty identifier, RootClass entity, Identity mappedId) { Mapping result = null; + GrailsHibernatePersistentEntity domainClass = null; if (identifier.getOwner() instanceof GrailsHibernatePersistentEntity) { - result = ((GrailsHibernatePersistentEntity) identifier.getOwner()).getMappedForm(); + domainClass = (GrailsHibernatePersistentEntity) identifier.getOwner(); + result = domainClass.getMappedForm(); } boolean useSequence = result != null && result.isTablePerConcreteClass(); // create the id value - BasicValue id = basicValueIdCreator.getBasicValueId(entity, mappedId, useSequence); + BasicValueIdCreator idCreator = this.basicValueIdCreator != null ? this.basicValueIdCreator : new BasicValueIdCreator(metadataBuildingContext, jdbcEnvironment, domainClass, entity); + BasicValue id = idCreator.getBasicValueId(entity, mappedId, useSequence); Property idProperty = new Property(); idProperty.setName(identifier.getName()); diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/SimpleValueBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/SimpleValueBinder.java index 3cb01638c3..1915843039 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/SimpleValueBinder.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/SimpleValueBinder.java @@ -21,6 +21,7 @@ import org.grails.orm.hibernate.cfg.PropertyConfig; public class SimpleValueBinder { + private final PersistentEntityNamingStrategy namingStrategy; private final ColumnConfigToColumnBinder columnConfigToColumnBinder ; private final ColumnBinder columnBinder; private final PersistentPropertyToPropertyConfig persistentPropertyToPropertyConfig; @@ -30,17 +31,23 @@ public class SimpleValueBinder { private static final String SEQUENCE_KEY = "sequence"; public SimpleValueBinder(PersistentEntityNamingStrategy namingStrategy) { + this(namingStrategy, new PersistentPropertyToPropertyConfig()); + } + + protected SimpleValueBinder(PersistentEntityNamingStrategy namingStrategy, PersistentPropertyToPropertyConfig persistentPropertyToPropertyConfig) { + this.namingStrategy = namingStrategy; + this.persistentPropertyToPropertyConfig = persistentPropertyToPropertyConfig; this.columnConfigToColumnBinder = new ColumnConfigToColumnBinder(); this.columnBinder = new ColumnBinder(namingStrategy); - this.persistentPropertyToPropertyConfig = new PersistentPropertyToPropertyConfig(); this.typeNameProvider = new TypeNameProvider(); - } - protected SimpleValueBinder(ColumnConfigToColumnBinder columnConfigToColumnBinder - , ColumnBinder columnBinder - , PersistentPropertyToPropertyConfig persistentPropertyToPropertyConfig - ,TypeNameProvider typeNameProvider) { + protected SimpleValueBinder(PersistentEntityNamingStrategy namingStrategy, + ColumnConfigToColumnBinder columnConfigToColumnBinder, + ColumnBinder columnBinder, + PersistentPropertyToPropertyConfig persistentPropertyToPropertyConfig, + TypeNameProvider typeNameProvider) { + this.namingStrategy = namingStrategy; this.columnConfigToColumnBinder = columnConfigToColumnBinder; this.columnBinder = columnBinder; this.persistentPropertyToPropertyConfig = persistentPropertyToPropertyConfig; diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/MappingSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/MappingSpec.groovy index c6aa7e4ba6..13cbb3d873 100644 --- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/MappingSpec.groovy +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/MappingSpec.groovy @@ -19,11 +19,11 @@ class MappingSpec extends HibernateGormDatastoreSpec { def mapping = (Mapping) entity.getMappedForm() def property = entity.getPropertyByName(propertyName) - when: "The method is called on the mapping object" - def result = mapping.isCompositeIdProperty(property) + when: "The method is called on the property itself" + def resultProperty = property.isCompositeIdProperty() - then: "The result is as expected" - result == expectedResult + then: "The results are as expected" + resultProperty == expectedResult where: description | domainClass | propertyName | expectedResult @@ -33,6 +33,27 @@ class MappingSpec extends HibernateGormDatastoreSpec { "a property from a simple id class" | SimpleIdBook | 'title' | false } + @Unroll + void "test isIdentityProperty should return #expectedResult for #description"() { + given: "A persistent entity and its property" + def binder = grailsDomainBinder + def entity = createPersistentEntity(domainClass, binder) + def property = entity.getPropertyByName(propertyName) + + when: "The method is called on the property itself" + def resultProperty = property.isIdentityProperty() + + then: "The result is as expected" + resultProperty == expectedResult + + where: + description | domainClass | propertyName | expectedResult + "the identity property" | SimpleIdBook | 'id' | true + "a non-identity property" | SimpleIdBook | 'title' | false + "the identity in composite entity" | CompositeIdBook | 'id' | true + "a property in composite identity" | CompositeIdBook | 'title' | false + } + } // --- Test Domain Classes --- diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ComponentBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ComponentBinderSpec.groovy new file mode 100644 index 0000000000..f190604e68 --- /dev/null +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ComponentBinderSpec.groovy @@ -0,0 +1,65 @@ +package org.grails.orm.hibernate.cfg.domainbinding + +import grails.gorm.specs.HibernateGormDatastoreSpec +import org.grails.datastore.mapping.model.PersistentProperty +import org.grails.datastore.mapping.model.types.Embedded +import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity +import org.grails.orm.hibernate.cfg.MappingCacheHolder +import org.hibernate.boot.spi.InFlightMetadataCollector +import org.hibernate.mapping.Component +import org.hibernate.mapping.PersistentClass +import org.hibernate.mapping.RootClass +import spock.lang.Subject + +class ComponentBinderSpec extends HibernateGormDatastoreSpec { + + MappingCacheHolder mappingCacheHolder = Mock(MappingCacheHolder) + ComponentPropertyBinder componentPropertyBinder = Mock(ComponentPropertyBinder) + + @Subject + ComponentBinder binder + + def setup() { + binder = new ComponentBinder(mappingCacheHolder, componentPropertyBinder) + } + + def "should bind component and its properties"() { + given: + def metadataBuildingContext = getGrailsDomainBinder().getMetadataBuildingContext() + def root = new RootClass(metadataBuildingContext) + root.setEntityName("MyEntity") + def component = new Component(metadataBuildingContext, root) + + def embeddedProp = GroovyMock(Embedded) + def associatedEntity = GroovyMock(GrailsHibernatePersistentEntity) + def prop1 = GroovyMock(PersistentProperty) + + embeddedProp.getType() >> Address + embeddedProp.getName() >> "address" + embeddedProp.getAssociatedEntity() >> associatedEntity + embeddedProp.getOwner() >> Mock(GrailsHibernatePersistentEntity) { + getJavaClass() >> MyEntity + } + + associatedEntity.getName() >> "Address" + associatedEntity.getPersistentProperties() >> [prop1] + associatedEntity.getIdentity() >> null + + prop1.getName() >> "street" + prop1.getType() >> String + + def mappings = metadataBuildingContext.getMetadataCollector() + + when: + binder.bindComponent(component, embeddedProp, true, mappings, "sessionFactory") + + then: + component.getComponentClassName() == Address.name + component.getRoleName() == Address.name + ".address" + 1 * mappingCacheHolder.cacheMapping(associatedEntity) + 1 * componentPropertyBinder.bindComponentProperty(component, embeddedProp, prop1, _ as PersistentClass, "address", _, mappings, "sessionFactory") + } + + static class MyEntity {} + static class Address {} +} diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ComponentPropertyBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ComponentPropertyBinderSpec.groovy new file mode 100644 index 0000000000..a8e5db3bf2 --- /dev/null +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ComponentPropertyBinderSpec.groovy @@ -0,0 +1,230 @@ +package org.grails.orm.hibernate.cfg.domainbinding + +import grails.gorm.specs.HibernateGormDatastoreSpec +import org.grails.datastore.mapping.model.PersistentEntity +import org.grails.datastore.mapping.model.PersistentProperty +import org.grails.datastore.mapping.model.types.Association +import org.grails.datastore.mapping.model.types.Embedded +import org.grails.datastore.mapping.model.types.ManyToOne as GormManyToOne +import org.grails.datastore.mapping.model.types.OneToOne as GormOneToOne +import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity +import org.grails.orm.hibernate.cfg.Mapping +import org.grails.orm.hibernate.cfg.MappingCacheHolder +import org.grails.orm.hibernate.cfg.PersistentEntityNamingStrategy +import org.grails.orm.hibernate.cfg.PropertyConfig +import org.grails.orm.hibernate.cfg.domainbinding.collectionType.CollectionHolder +import org.hibernate.boot.spi.InFlightMetadataCollector +import org.hibernate.mapping.BasicValue +import org.hibernate.mapping.Column +import org.hibernate.mapping.Component +import org.hibernate.mapping.ManyToOne as HibernateManyToOne +import org.hibernate.mapping.OneToOne as HibernateOneToOne +import org.hibernate.mapping.PersistentClass +import org.hibernate.mapping.Property +import org.hibernate.mapping.RootClass +import org.hibernate.mapping.Table +import spock.lang.Subject + +class ComponentPropertyBinderSpec extends HibernateGormDatastoreSpec { + + PersistentEntityNamingStrategy namingStrategy = Mock(PersistentEntityNamingStrategy) + MappingCacheHolder mappingCacheHolder = Mock(MappingCacheHolder) + CollectionHolder collectionHolder = new CollectionHolder([:]) + EnumTypeBinder enumTypeBinder = Mock(EnumTypeBinder) + PropertyFromValueCreator propertyFromValueCreator = Mock(PropertyFromValueCreator) + ComponentBinder componentBinder = Mock(ComponentBinder) + PersistentPropertyToPropertyConfig persistentPropertyToPropertyConfig = new PersistentPropertyToPropertyConfig() + + @Subject + ComponentPropertyBinder binder + + def setup() { + binder = new ComponentPropertyBinder( + getGrailsDomainBinder().getMetadataBuildingContext(), + namingStrategy, + mappingCacheHolder, + collectionHolder, + enumTypeBinder, + propertyFromValueCreator, + componentBinder, + persistentPropertyToPropertyConfig + ) + } + + private void setupProperty(PersistentProperty prop, String name, Mapping mapping, PersistentEntity owner) { + prop.getName() >> name + prop.getOwner() >> owner + def config = new PropertyConfig() + mapping.getColumns().put(name, config) + prop.getMappedForm() >> config + } + + def "should bind simple property"() { + given: + def metadataBuildingContext = getGrailsDomainBinder().getMetadataBuildingContext() + def root = new RootClass(metadataBuildingContext) + def component = new Component(metadataBuildingContext, root) + def table = new Table("my_table") + def ownerEntity = GroovyMock(GrailsHibernatePersistentEntity) + ownerEntity.isRoot() >> true + def currentGrailsProp = GroovyMock(PersistentProperty) + def componentProperty = GroovyMock(PersistentProperty) + def mappings = Mock(InFlightMetadataCollector) + def hibernateProperty = new Property() + hibernateProperty.setName("street") + + def mapping = new Mapping() + ownerEntity.getMappedForm() >> mapping + currentGrailsProp.getOwner() >> ownerEntity + currentGrailsProp.getType() >> String + setupProperty(currentGrailsProp, "street", mapping, ownerEntity) + + propertyFromValueCreator.createProperty(_ as BasicValue, currentGrailsProp) >> hibernateProperty + + when: + binder.bindComponentProperty(component, componentProperty, currentGrailsProp, root, "address", table, mappings, "sessionFactory") + + then: + 1 * propertyFromValueCreator.createProperty(_ as BasicValue, currentGrailsProp) >> hibernateProperty + component.getProperty("street") == hibernateProperty + } + + def "should bind many-to-one property"() { + given: + def metadataBuildingContext = getGrailsDomainBinder().getMetadataBuildingContext() + def root = new RootClass(metadataBuildingContext) + def component = new Component(metadataBuildingContext, root) + def table = new Table("my_table") + def ownerEntity = GroovyMock(GrailsHibernatePersistentEntity) + ownerEntity.isRoot() >> true + def currentGrailsProp = GroovyMock(GormManyToOne) + def componentProperty = GroovyMock(PersistentProperty) + def mappings = Mock(InFlightMetadataCollector) + def hibernateProperty = new Property() + hibernateProperty.setName("owner") + + def mapping = new Mapping() + ownerEntity.getMappedForm() >> mapping + currentGrailsProp.getOwner() >> ownerEntity + currentGrailsProp.getAssociatedEntity() >> Mock(GrailsHibernatePersistentEntity) { getName() >> "Owner" } + currentGrailsProp.getType() >> Object + setupProperty(currentGrailsProp, "owner", mapping, ownerEntity) + + propertyFromValueCreator.createProperty(_ as HibernateManyToOne, currentGrailsProp) >> hibernateProperty + + when: + binder.bindComponentProperty(component, componentProperty, currentGrailsProp, root, "address", table, mappings, "sessionFactory") + + then: + 1 * propertyFromValueCreator.createProperty(_ as HibernateManyToOne, currentGrailsProp) >> hibernateProperty + component.getProperty("owner") == hibernateProperty + } + + def "should bind one-to-one property"() { + given: + def metadataBuildingContext = getGrailsDomainBinder().getMetadataBuildingContext() + def root = new RootClass(metadataBuildingContext) + def component = new Component(metadataBuildingContext, root) + def table = new Table("my_table") + def ownerEntity = GroovyMock(GrailsHibernatePersistentEntity) + ownerEntity.isRoot() >> true + def currentGrailsProp = GroovyMock(GormOneToOne) + def componentProperty = GroovyMock(PersistentProperty) + def mappings = Mock(InFlightMetadataCollector) + def hibernateProperty = new Property() + hibernateProperty.setName("detail") + + def mapping = new Mapping() + ownerEntity.getMappedForm() >> mapping + currentGrailsProp.getOwner() >> ownerEntity + currentGrailsProp.getInverseSide() >> Mock(Association) { + isHasOne() >> false + getOwner() >> Mock(GrailsHibernatePersistentEntity) { getName() >> "Other" } + getName() >> "other" + } + currentGrailsProp.getType() >> Object + currentGrailsProp.canBindOneToOneWithSingleColumnAndForeignKey() >> true + setupProperty(currentGrailsProp, "detail", mapping, ownerEntity) + + propertyFromValueCreator.createProperty(_ as HibernateOneToOne, currentGrailsProp) >> hibernateProperty + + when: + binder.bindComponentProperty(component, componentProperty, currentGrailsProp, root, "address", table, mappings, "sessionFactory") + + then: + 1 * propertyFromValueCreator.createProperty(_ as HibernateOneToOne, currentGrailsProp) >> hibernateProperty + component.getProperty("detail") == hibernateProperty + } + + def "should bind enum property"() { + given: + def metadataBuildingContext = getGrailsDomainBinder().getMetadataBuildingContext() + def root = new RootClass(metadataBuildingContext) + def component = new Component(metadataBuildingContext, root) + def table = new Table("my_table") + def ownerEntity = GroovyMock(GrailsHibernatePersistentEntity) + ownerEntity.isRoot() >> true + def currentGrailsProp = GroovyMock(PersistentProperty) + def componentProperty = GroovyMock(PersistentProperty) + def mappings = Mock(InFlightMetadataCollector) + def hibernateProperty = new Property() + hibernateProperty.setName("type") + + def mapping = new Mapping() + ownerEntity.getMappedForm() >> mapping + currentGrailsProp.getOwner() >> ownerEntity + currentGrailsProp.getType() >> MyEnum + setupProperty(currentGrailsProp, "type", mapping, ownerEntity) + + namingStrategy.resolveColumnName("type") >> "type_col" + namingStrategy.resolveColumnName("address") >> "address" + propertyFromValueCreator.createProperty(_ as BasicValue, currentGrailsProp) >> hibernateProperty + + when: + binder.bindComponentProperty(component, componentProperty, currentGrailsProp, root, "address", table, mappings, "sessionFactory") + + then: + 1 * enumTypeBinder.bindEnumType(currentGrailsProp, MyEnum, _ as BasicValue, "address_type_col") + 1 * propertyFromValueCreator.createProperty(_ as BasicValue, currentGrailsProp) >> hibernateProperty + component.getProperty("type") == hibernateProperty + } + + def "should set columns to nullable when component property is nullable"() { + given: + def metadataBuildingContext = getGrailsDomainBinder().getMetadataBuildingContext() + def root = new RootClass(metadataBuildingContext) + def component = new Component(metadataBuildingContext, root) + def table = new Table("my_table") + def ownerEntity = GroovyMock(GrailsHibernatePersistentEntity) + ownerEntity.isRoot() >> true + def currentGrailsProp = GroovyMock(PersistentProperty) + def componentProperty = GroovyMock(PersistentProperty) + def ownerEntityGHPE = GroovyMock(GrailsHibernatePersistentEntity) + def mappings = Mock(InFlightMetadataCollector) + def hibernateProperty = new Property() + hibernateProperty.setName("street") + def value = new BasicValue(metadataBuildingContext, table) + def column = new Column("col1") + value.addColumn(column) + + def mapping = new Mapping() + ownerEntityGHPE.getMappedForm() >> mapping + currentGrailsProp.getOwner() >> ownerEntityGHPE + currentGrailsProp.getType() >> String + setupProperty(currentGrailsProp, "street", mapping, ownerEntityGHPE) + + componentProperty.getOwner() >> ownerEntity + ownerEntity.isComponentPropertyNullable(componentProperty) >> true + + propertyFromValueCreator.createProperty(value, currentGrailsProp) >> hibernateProperty + hibernateProperty.setValue(value) + + when: + binder.bindComponentProperty(component, componentProperty, currentGrailsProp, root, "address", table, mappings, "sessionFactory") + + then: + column.isNullable() + } + + enum MyEnum { VAL } +} \ No newline at end of file diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CompositeIdBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CompositeIdBinderSpec.groovy new file mode 100644 index 0000000000..855b48c1af --- /dev/null +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CompositeIdBinderSpec.groovy @@ -0,0 +1,95 @@ +package org.grails.orm.hibernate.cfg.domainbinding + +import grails.gorm.specs.HibernateGormDatastoreSpec +import org.grails.datastore.mapping.model.PersistentProperty +import org.grails.orm.hibernate.cfg.CompositeIdentity +import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity +import org.hibernate.boot.spi.InFlightMetadataCollector +import org.hibernate.mapping.Component +import org.hibernate.mapping.RootClass +import org.hibernate.mapping.Table +import spock.lang.Subject + +class CompositeIdBinderSpec extends HibernateGormDatastoreSpec { + + def componentPropertyBinder = Mock(ComponentPropertyBinder) + + @Subject + CompositeIdBinder binder + + def setup() { + binder = new CompositeIdBinder(getGrailsDomainBinder().getMetadataBuildingContext(), componentPropertyBinder) + } + + def "should bind composite id using property names from CompositeIdentity"() { + given: + def domainClass = GroovyMock(GrailsHibernatePersistentEntity) + def metadataBuildingContext = getGrailsDomainBinder().getMetadataBuildingContext() + def root = new RootClass(metadataBuildingContext) + root.setEntityName("MyEntity") + def mappings = metadataBuildingContext.getMetadataCollector() + + def compositeIdentity = new CompositeIdentity(propertyNames: ['prop1', 'prop2'] as String[]) + + def prop1 = GroovyMock(PersistentProperty) + def prop2 = GroovyMock(PersistentProperty) + def identifierProp = GroovyMock(PersistentProperty) + domainClass.getPropertyByName("prop1") >> prop1 + domainClass.getPropertyByName("prop2") >> prop2 + domainClass.getIdentity() >> identifierProp + domainClass.getName() >> "MyEntity" + + def table = new Table("my_entity") + root.setTable(table) + + when: + binder.bindCompositeId(domainClass, root, compositeIdentity, mappings, "sessionFactory") + + then: + root.getIdentifier() instanceof Component + root.getIdentifierMapper() instanceof Component + root.hasEmbeddedIdentifier() + 2 * componentPropertyBinder.bindComponentProperty(_ as Component, identifierProp, _ as PersistentProperty, root, "", table, mappings, "sessionFactory") + } + + def "should fallback to domainClass composite identity when CompositeIdentity is null"() { + given: + def domainClass = GroovyMock(GrailsHibernatePersistentEntity) + def metadataBuildingContext = getGrailsDomainBinder().getMetadataBuildingContext() + def root = new RootClass(metadataBuildingContext) + root.setEntityName("MyEntity") + def mappings = metadataBuildingContext.getMetadataCollector() + + def prop1 = GroovyMock(PersistentProperty) + def identifierProp = GroovyMock(PersistentProperty) + domainClass.getCompositeIdentity() >> ([prop1] as PersistentProperty[]) + domainClass.getIdentity() >> identifierProp + domainClass.getName() >> "MyEntity" + + def table = new Table("my_entity") + root.setTable(table) + + when: + binder.bindCompositeId(domainClass, root, null, mappings, "sessionFactory") + + then: + 1 * componentPropertyBinder.bindComponentProperty(_ as Component, identifierProp, prop1, root, "", table, mappings, "sessionFactory") + } + + def "should throw MappingException if no composite properties found"() { + given: + def domainClass = GroovyMock(GrailsHibernatePersistentEntity) + def metadataBuildingContext = getGrailsDomainBinder().getMetadataBuildingContext() + def root = new RootClass(metadataBuildingContext) + root.setEntityName("MyEntity") + def mappings = metadataBuildingContext.getMetadataCollector() + domainClass.getCompositeIdentity() >> null + domainClass.getName() >> "MyEntity" + + when: + binder.bindCompositeId(domainClass, root, null, mappings, "sessionFactory") + + then: + thrown(org.hibernate.MappingException) + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..717e8e846f --- /dev/null +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/IdentityBinderSpec.groovy @@ -0,0 +1,125 @@ +package org.grails.orm.hibernate.cfg.domainbinding + +import org.grails.datastore.mapping.model.PersistentProperty +import org.grails.orm.hibernate.cfg.CompositeIdentity +import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity +import org.grails.orm.hibernate.cfg.Identity +import org.grails.orm.hibernate.cfg.Mapping +import org.hibernate.boot.spi.InFlightMetadataCollector +import org.hibernate.mapping.RootClass +import spock.lang.Specification +import spock.lang.Subject + +class IdentityBinderSpec extends Specification { + + def simpleIdBinder = Mock(SimpleIdBinder) + def compositeIdBinder = Mock(CompositeIdBinder) + + @Subject + IdentityBinder binder = new IdentityBinder(simpleIdBinder, compositeIdBinder) + + def "should delegate to simpleIdBinder when mapping is null and domainClass has simple identity"() { + given: + def domainClass = GroovyMock(GrailsHibernatePersistentEntity) + def root = GroovyMock(RootClass) + def mappings = GroovyMock(InFlightMetadataCollector) + def identifierProp = GroovyMock(PersistentProperty) + domainClass.getIdentity() >> identifierProp + domainClass.getCompositeIdentity() >> null + + when: + binder.bindIdentity(domainClass, root, mappings, null, "sessionFactory") + + then: + 1 * simpleIdBinder.bindSimpleId(identifierProp, root, null) + } + + def "should delegate to compositeIdBinder when mapping is null and domainClass has composite identity"() { + given: + def domainClass = GroovyMock(GrailsHibernatePersistentEntity) + def root = GroovyMock(RootClass) + def mappings = GroovyMock(InFlightMetadataCollector) + def compositeProps = [GroovyMock(PersistentProperty)] as PersistentProperty[] + domainClass.getCompositeIdentity() >> compositeProps + + when: + binder.bindIdentity(domainClass, root, mappings, null, "sessionFactory") + + then: + 1 * compositeIdBinder.bindCompositeId(domainClass, root, null, mappings, "sessionFactory") + } + + def "should delegate to compositeIdBinder when mapping specifies composite identity"() { + given: + def domainClass = GroovyMock(GrailsHibernatePersistentEntity) + def root = GroovyMock(RootClass) + def mappings = GroovyMock(InFlightMetadataCollector) + def gormMapping = GroovyMock(Mapping) + def compositeIdentity = GroovyMock(CompositeIdentity) + gormMapping.getIdentity() >> compositeIdentity + + when: + binder.bindIdentity(domainClass, root, mappings, gormMapping, "sessionFactory") + + then: + 1 * compositeIdBinder.bindCompositeId(domainClass, root, compositeIdentity, mappings, "sessionFactory") + } + + def "should delegate to simpleIdBinder when mapping specifies simple identity"() { + given: + def domainClass = GroovyMock(GrailsHibernatePersistentEntity) + def root = GroovyMock(RootClass) + def mappings = GroovyMock(InFlightMetadataCollector) + def gormMapping = GroovyMock(Mapping) + def identity = new Identity(name: "foo") + gormMapping.getIdentity() >> identity + def identifierProp = GroovyMock(PersistentProperty) + domainClass.getPropertyByName("foo") >> identifierProp + domainClass.getIdentity() >> identifierProp + domainClass.getName() >> "MyEntity" + + when: + binder.bindIdentity(domainClass, root, mappings, gormMapping, "sessionFactory") + + then: + 1 * simpleIdBinder.bindSimpleId(identifierProp, root, identity) + } + + def "should throw MappingException when mapping specifies a non-existent identifier property"() { + given: + def domainClass = GroovyMock(GrailsHibernatePersistentEntity) + def root = GroovyMock(RootClass) + def mappings = GroovyMock(InFlightMetadataCollector) + def gormMapping = GroovyMock(Mapping) + def identity = new Identity(name: "nonExistent") + gormMapping.getIdentity() >> identity + domainClass.getName() >> "MyEntity" + domainClass.getPropertyByName("nonExistent") >> null + + when: + binder.bindIdentity(domainClass, root, mappings, gormMapping, "sessionFactory") + + then: + thrown(org.hibernate.MappingException) + } + + def "should not lookup property by name if identity name matches domain class name"() { + given: + def domainClass = GroovyMock(GrailsHibernatePersistentEntity) + def root = GroovyMock(RootClass) + def mappings = GroovyMock(InFlightMetadataCollector) + def gormMapping = GroovyMock(Mapping) + def identity = new Identity(name: "MyEntity") + gormMapping.getIdentity() >> identity + def identifierProp = GroovyMock(PersistentProperty) + domainClass.getIdentity() >> identifierProp + domainClass.getName() >> "MyEntity" + + when: + binder.bindIdentity(domainClass, root, mappings, gormMapping, "sessionFactory") + + then: + 0 * domainClass.getPropertyByName(_) + 1 * simpleIdBinder.bindSimpleId(identifierProp, root, identity) + } +} diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/OneToOneBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/OneToOneBinderSpec.groovy new file mode 100644 index 0000000000..2d17639bda --- /dev/null +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/OneToOneBinderSpec.groovy @@ -0,0 +1,121 @@ +package org.grails.orm.hibernate.cfg.domainbinding + +import grails.gorm.specs.HibernateGormDatastoreSpec +import org.grails.datastore.mapping.model.types.OneToOne as GormOneToOne +import org.grails.datastore.mapping.model.PersistentEntity +import org.grails.orm.hibernate.cfg.PropertyConfig +import org.hibernate.FetchMode +import org.hibernate.mapping.OneToOne as HibernateOneToOne +import org.hibernate.mapping.RootClass +import org.hibernate.type.ForeignKeyDirection +import spock.lang.Subject + +class OneToOneBinderSpec extends HibernateGormDatastoreSpec { + + @Subject + OneToOneBinder binder + + PersistentPropertyToPropertyConfig mockConfigReader = Mock(PersistentPropertyToPropertyConfig) + SimpleValueBinder mockSimpleValueBinder = Mock(SimpleValueBinder) + + def setup() { + binder = new OneToOneBinder(getGrailsDomainBinder().getNamingStrategy(), mockConfigReader, mockSimpleValueBinder) + } + + def "should bind one-to-one mapping with defaults"() { + given: + def metadataBuildingContext = getGrailsDomainBinder().getMetadataBuildingContext() + def ownerRoot = new RootClass(metadataBuildingContext) + def hibernateOneToOne = new HibernateOneToOne(metadataBuildingContext, null, ownerRoot) + + def gormOneToOne = GroovyMock(GormOneToOne) + def otherSide = GroovyMock(GormOneToOne) + def owner = GroovyMock(PersistentEntity) + def otherOwner = GroovyMock(PersistentEntity) + + gormOneToOne.getInverseSide() >> otherSide + gormOneToOne.getName() >> "myOneToOne" + gormOneToOne.getOwner() >> owner + + otherSide.isHasOne() >> false + otherSide.getOwner() >> otherOwner + otherSide.getName() >> "otherSide" + + otherOwner.getName() >> "OtherEntity" + + mockConfigReader.toPropertyConfig(gormOneToOne) >> new PropertyConfig() + + when: + binder.bindOneToOne(gormOneToOne, hibernateOneToOne, "") + + then: + !hibernateOneToOne.isConstrained() + hibernateOneToOne.getForeignKeyType() == ForeignKeyDirection.TO_PARENT + hibernateOneToOne.isAlternateUniqueKey() + hibernateOneToOne.getFetchMode() == FetchMode.DEFAULT + hibernateOneToOne.getReferencedEntityName() == "OtherEntity" + hibernateOneToOne.getPropertyName() == "myOneToOne" + hibernateOneToOne.getReferencedPropertyName() == "otherSide" + } + + def "should bind constrained one-to-one mapping when other side is hasOne"() { + given: + def metadataBuildingContext = getGrailsDomainBinder().getMetadataBuildingContext() + def ownerRoot = new RootClass(metadataBuildingContext) + def hibernateOneToOne = new HibernateOneToOne(metadataBuildingContext, null, ownerRoot) + + def gormOneToOne = GroovyMock(GormOneToOne) + def otherSide = GroovyMock(GormOneToOne) + def owner = GroovyMock(PersistentEntity) + def otherOwner = GroovyMock(PersistentEntity) + + gormOneToOne.getInverseSide() >> otherSide + gormOneToOne.getName() >> "myOneToOne" + gormOneToOne.getOwner() >> owner + + otherSide.isHasOne() >> true + otherSide.getOwner() >> otherOwner + + otherOwner.getName() >> "OtherEntity" + + def propertyConfig = new PropertyConfig() + mockConfigReader.toPropertyConfig(gormOneToOne) >> propertyConfig + mockConfigReader.toPropertyConfig(otherSide) >> propertyConfig // In case SimpleValueBinder needs it too + + when: + binder.bindOneToOne(gormOneToOne, hibernateOneToOne, "") + + then: + hibernateOneToOne.isConstrained() + hibernateOneToOne.getForeignKeyType() == ForeignKeyDirection.FROM_PARENT + hibernateOneToOne.getReferencedEntityName() == "OtherEntity" + } + + def "should respect fetch mode from mapping"() { + given: + def metadataBuildingContext = getGrailsDomainBinder().getMetadataBuildingContext() + def ownerRoot = new RootClass(metadataBuildingContext) + def hibernateOneToOne = new HibernateOneToOne(metadataBuildingContext, null, ownerRoot) + + def gormOneToOne = GroovyMock(GormOneToOne) + def otherSide = GroovyMock(GormOneToOne) + def owner = GroovyMock(PersistentEntity) + def otherOwner = GroovyMock(PersistentEntity) + + def propertyConfig = new PropertyConfig() + propertyConfig.setFetch("join") + + gormOneToOne.getInverseSide() >> otherSide + gormOneToOne.getOwner() >> owner + otherSide.getOwner() >> otherOwner + otherSide.isHasOne() >> false + + mockConfigReader.toPropertyConfig(gormOneToOne) >> propertyConfig + + when: + binder.bindOneToOne(gormOneToOne, hibernateOneToOne, "") + + then: + hibernateOneToOne.getFetchMode() == FetchMode.JOIN + } +} diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/PropertyFromValueCreatorSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/PropertyFromValueCreatorSpec.groovy new file mode 100644 index 0000000000..2f48fcb464 --- /dev/null +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/PropertyFromValueCreatorSpec.groovy @@ -0,0 +1,55 @@ +package org.grails.orm.hibernate.cfg.domainbinding + +import org.hibernate.mapping.Property +import org.hibernate.mapping.Table +import org.hibernate.mapping.Value +import org.grails.datastore.mapping.model.PersistentProperty +import spock.lang.Specification + +class PropertyFromValueCreatorSpec extends Specification { + + def "should create a property from a value"() { + given: + def propertyBinder = Mock(PropertyBinder) + def creator = new PropertyFromValueCreator(propertyBinder) + + def value = Mock(Value) + def grailsProperty = Mock(PersistentProperty) + def table = new Table("my_table") + + grailsProperty.getOwnerClassName() >> "com.example.MyEntity" + grailsProperty.getName() >> "myProp" + value.getTable() >> table + + when: + Property prop = creator.createProperty(value, grailsProperty) + + then: + 1 * value.setTypeUsingReflection("com.example.MyEntity", "myProp") + 1 * value.createForeignKey() + 1 * propertyBinder.bindProperty(grailsProperty, _ as Property) + prop.getValue() == value + } + + def "should create a property without foreign key when table is null"() { + given: + def propertyBinder = Mock(PropertyBinder) + def creator = new PropertyFromValueCreator(propertyBinder) + + def value = Mock(Value) + def grailsProperty = Mock(PersistentProperty) + + grailsProperty.getOwnerClassName() >> "com.example.MyEntity" + grailsProperty.getName() >> "myProp" + value.getTable() >> null + + when: + Property prop = creator.createProperty(value, grailsProperty) + + then: + 1 * value.setTypeUsingReflection("com.example.MyEntity", "myProp") + 0 * value.createForeignKey() + 1 * propertyBinder.bindProperty(grailsProperty, _ as Property) + prop.getValue() == value + } +} \ No newline at end of file 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 275242d3af..42df9d0972 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 @@ -18,7 +18,7 @@ class SimpleIdBinderSpec extends HibernateGormDatastoreSpec { JdbcEnvironment jdbcEnvironment def simpleValueBinder def propertyBinder - def basicValueIdcreator + def basicValueIdCreator def simpleIdBinder @@ -27,8 +27,8 @@ class SimpleIdBinderSpec extends HibernateGormDatastoreSpec { jdbcEnvironment = getGrailsDomainBinder().getJdbcEnvironment() // Use a Mock for BasicValueIdCreator and return a BasicValue based on the RootClass table - basicValueIdcreator = Mock(BasicValueIdCreator) - basicValueIdcreator.getBasicValueId(*_) >> { RootClass rc, Identity id, boolean useSeq -> + basicValueIdCreator = Mock(BasicValueIdCreator) + basicValueIdCreator.getBasicValueId(*_) >> { RootClass rc, Identity id, boolean useSeq -> new BasicValue(metadataBuildingContext, rc.getTable()) } @@ -36,7 +36,7 @@ class SimpleIdBinderSpec extends HibernateGormDatastoreSpec { simpleValueBinder = Mock(SimpleValueBinder) propertyBinder = Mock(PropertyBinder) - simpleIdBinder = new SimpleIdBinder(basicValueIdcreator, simpleValueBinder, propertyBinder) + simpleIdBinder = new SimpleIdBinder(basicValueIdCreator, simpleValueBinder, propertyBinder) } def "bindSimpleId with identity generator"() { diff --git a/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/model/AbstractPersistentEntity.java b/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/model/AbstractPersistentEntity.java index 2fa5b53aae..c14ef183b0 100644 --- a/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/model/AbstractPersistentEntity.java +++ b/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/model/AbstractPersistentEntity.java @@ -300,7 +300,11 @@ public abstract class AbstractPersistentEntity<T extends Entity> implements Pers } public boolean isIdentityName(String propertyName) { - return getIdentity().getName().equals(propertyName); + PersistentProperty identity = getIdentity(); + if (identity != null) { + return identity.getName().equals(propertyName); + } + return GormProperties.IDENTITY.equals(propertyName); } public PersistentEntity getParentEntity() { diff --git a/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/model/PersistentProperty.java b/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/model/PersistentProperty.java index 5f9c35fce3..e9a1d87dcc 100644 --- a/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/model/PersistentProperty.java +++ b/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/model/PersistentProperty.java @@ -131,4 +131,33 @@ public interface PersistentProperty<T extends Property> { return SortedSet.class.isAssignableFrom(this.getType()); } + /** + * @return Whether this property is part of a composite identifier + */ + default boolean isCompositeIdProperty() { + PersistentProperty[] compositeId = getOwner().getCompositeIdentity(); + if (compositeId != null) { + for (PersistentProperty p : compositeId) { + if (p.getName().equals(getName())) { + return true; + } + } + } + return false; + } + + /** + * @return Whether this property is the identity + */ + default boolean isIdentityProperty() { + return getOwner().isIdentityName(getName()); + } + + default String getOwnerClassName() { + return ofNullable(getOwner()) + .map(PersistentEntity::getJavaClass) + .map(Class::getName) + .orElseThrow(() -> new IllegalMappingException("Property [" + getName() + "] has no owner entity defined")); + } + }
