You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by bo...@apache.org on 2026/10/06 18:38:10 UTC

(grails-core) 02/14: fix: bind a GORM entity from annotatedClasses through the generated domain classes

This is an automated email from the ASF dual-hosted git repository.

borinquenkid pushed a commit to branch 9.0.x-h7-improvement-phase3
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 84c2007a1790bf206b64b269b9c42b0507e8c618
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Tue Oct 6 09:52:56 2026 -0500

    fix: bind a GORM entity from annotatedClasses through the generated domain classes
    
    Hibernate keeps the first class it is given for a name. A GORM entity registered through
    hibernate.annotatedClasses was handed to Hibernate's own sources as the real class, which
    Hibernate cannot read as an entity, so it hid the class generated under the same name and
    the entity stayed unmapped (Could not resolve entity class). With the generated domain
    classes on, such an entity is only registered for the mapping context; JPA entities and
    plain classes are still handed over. Found by running AdditionalClassBindingSpec in
    generated mode.
    
    Co-Authored-By: Claude Sonnet 5.5 <no...@anthropic.com>
---
 .../HibernateMappingContextConfiguration.groovy    | 13 ++++
 ...neratedDomainClassesAnnotatedClassesSpec.groovy | 77 ++++++++++++++++++++++
 2 files changed, 90 insertions(+)

diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/HibernateMappingContextConfiguration.groovy b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/HibernateMappingContextConfiguration.groovy
index 08df1007d7..f3c1eee3e0 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/HibernateMappingContextConfiguration.groovy
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/HibernateMappingContextConfiguration.groovy
@@ -112,6 +112,9 @@ class HibernateMappingContextConfiguration extends Configuration
      * Whether Hibernate's annotation binder binds classes generated from the GORM mapping instead of the domain binder
      * building Hibernate's boot model by hand. The real domain classes are still what is persisted and loaded.
      *
+     * <p>Set it before the annotated classes are added: with the switch on, {@link #addAnnotatedClass} keeps the GORM entities
+     * that are not JPA entities away from Hibernate's own sources (see there).</p>
+     *
      * @param generatedDomainClasses {@code true} to bind generated classes; {@code false} (the default) for the domain binder
      */
     void setGeneratedDomainClasses(boolean generatedDomainClasses) {
@@ -206,9 +209,19 @@ class HibernateMappingContextConfiguration extends Configuration
         return this
     }
 
+    /**
+     * Registers the class for the mapping context and hands it to Hibernate's own sources. With
+     * {@link #setGeneratedDomainClasses generated domain classes} a GORM entity that is not a JPA entity is not handed to
+     * Hibernate: the class generated for it has the same name, and Hibernate keeps the first class it is given for a name, so
+     * the real class, which Hibernate cannot read as an entity, would hide the generated one and leave the entity unmapped.
+     * The entity is bound through the mapping context as usual.
+     */
     @Override
     Configuration addAnnotatedClass(Class annotatedClass) {
         additionalClasses.add(annotatedClass)
+        if (generatedDomainClasses && GormEntity.isAssignableFrom(annotatedClass) && !annotatedClass.isAnnotationPresent(Entity)) {
+            return this
+        }
         return super.addAnnotatedClass(annotatedClass)
     }
 
diff --git a/grails-data-hibernate7/core/src/test/groovy/grails/gorm/tests/generated/GeneratedDomainClassesAnnotatedClassesSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/grails/gorm/tests/generated/GeneratedDomainClassesAnnotatedClassesSpec.groovy
new file mode 100644
index 0000000000..bd86591f5c
--- /dev/null
+++ b/grails-data-hibernate7/core/src/test/groovy/grails/gorm/tests/generated/GeneratedDomainClassesAnnotatedClassesSpec.groovy
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package grails.gorm.tests.generated
+
+import grails.gorm.annotation.Entity
+import spock.lang.AutoCleanup
+import spock.lang.Shared
+import spock.lang.Specification
+
+import org.grails.datastore.mapping.core.DatastoreUtils
+import org.grails.orm.hibernate.HibernateDatastore
+
+/**
+ * A GORM entity that reaches the datastore through {@code hibernate.annotatedClasses} is bound by the generated-domain-class
+ * path like one passed to the constructor: Hibernate keeps the first class it is given for a name, so the real class must not
+ * hide the class generated under the same name.
+ */
+class GeneratedDomainClassesAnnotatedClassesSpec extends Specification {
+
+    @Shared
+    @AutoCleanup
+    HibernateDatastore datastore = new HibernateDatastore(
+            DatastoreUtils.createPropertyResolver([
+                    'hibernate.generatedDomainClasses': true,
+                    'hibernate.annotatedClasses'      : [GdcAnnotatedThing],
+                    'dataSource.url'                  : 'jdbc:h2:mem:gdcAnnotatedClasses;LOCK_TIMEOUT=10000',
+                    'dataSource.dbCreate'             : 'create-drop',
+                    'hibernate.hbm2ddl.auto'          : 'create-drop',
+            ]),
+            GdcConstructorThing)
+
+    void "an entity registered through annotatedClasses is bound under its own name and persists real instances"() {
+        when:
+        GdcAnnotatedThing saved = GdcAnnotatedThing.withNewTransaction {
+            new GdcAnnotatedThing(name: 'a').save(flush: true)
+        }
+        GdcAnnotatedThing loaded = GdcAnnotatedThing.withNewSession { GdcAnnotatedThing.get(saved.id) }
+
+        then:
+        datastore.sessionFactory.mappingMetamodel.getEntityDescriptor(GdcAnnotatedThing).entityName == GdcAnnotatedThing.name
+        loaded.class == GdcAnnotatedThing
+        loaded.name == 'a'
+    }
+
+    void "an entity passed to the constructor is bound next to it"() {
+        expect:
+        GdcConstructorThing.withNewSession { GdcConstructorThing.count() } == 0
+    }
+}
+
+@Entity
+class GdcAnnotatedThing {
+
+    String name
+}
+
+@Entity
+class GdcConstructorThing {
+
+    String name
+}