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"));
+    }
+
 }

Reply via email to