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/05 12:55:40 UTC
(grails-core) 23/29: feat: state lazy: true on simple properties in GrailsDomainGenerator
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 65247fed037656eb334fd3e75ab2c20b7005095e
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Mon Oct 5 04:39:16 2026 -0500
feat: state lazy: true on simple properties in GrailsDomainGenerator
PropertyBinder marks a Hibernate property lazy whenever the mapping says lazy: true
(its accessor is not limited to associations), so a simple or enum property mapped
lazy was a lazy attribute in the binder and an eager one in a generated model.
The generated field now carries @Basic(fetch = LAZY) in that case. An embedded
property mapped lazy is rejected by name, because an @Embedded cannot state it. The
differential now compares Property.lazy of every leaf and to-one association with
what Hibernate's annotation binder builds.
Co-Authored-By: Claude Sonnet 5.5 <no...@anthropic.com>
---
.../domainbinding/jpa/GrailsDomainGenerator.groovy | 9 ++
.../GrailsDomainGeneratorDifferentialSpec.groovy | 4 +
.../GrailsDomainGeneratorLazyPropertySpec.groovy | 107 +++++++++++++++++++++
3 files changed, 120 insertions(+)
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/jpa/GrailsDomainGenerator.groovy b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/jpa/GrailsDomainGenerator.groovy
index b4a855856a..b3748c0b96 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/jpa/GrailsDomainGenerator.groovy
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/jpa/GrailsDomainGenerator.groovy
@@ -27,6 +27,7 @@ import jakarta.persistence.AssociationOverride
import jakarta.persistence.AssociationOverrides
import jakarta.persistence.AttributeOverride
import jakarta.persistence.AttributeOverrides
+import jakarta.persistence.Basic
import jakarta.persistence.Cacheable
import jakarta.persistence.CascadeType
import jakarta.persistence.CollectionTable
@@ -1608,6 +1609,10 @@ class GrailsDomainGenerator {
if (!type.isRoot()) {
return "the embedded type [${type.name}] extends another persistent class"
}
+ if (property.isLazy()) {
+ return 'the property is mapped lazy: true, which the binder marks on the component as a lazy attribute and which ' +
+ 'annotations cannot state on an @Embedded'
+ }
if (visiting.contains(type.javaClass)) {
return "the embedded type [${type.name}] contains itself"
}
@@ -1861,6 +1866,10 @@ class GrailsDomainGenerator {
if (naturalMutable != null) {
annotations << AnnotationDescription.Builder.ofType(HibernateNaturalId).define('mutable', naturalMutable).build()
}
+ if (!(property instanceof HibernateSimpleIdentityProperty) && property.isLazy()) {
+ // PropertyBinder marks the property lazy when the mapping says lazy: true (the version never is)
+ annotations << AnnotationDescription.Builder.ofType(Basic).define('fetch', FetchType.LAZY).build()
+ }
// an identifier's type is decided with its generator, which the generator does not describe yet
TypeFacets type = property instanceof HibernateSimpleIdentityProperty ? null : typeFacets(property)
if (type != null) {
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/jpa/GrailsDomainGeneratorDifferentialSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/jpa/GrailsDomainGeneratorDifferentialSpec.groovy
index 8a3ca61215..436dba0edc 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/jpa/GrailsDomainGeneratorDifferentialSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/jpa/GrailsDomainGeneratorDifferentialSpec.groovy
@@ -1360,6 +1360,7 @@ class GrailsDomainGeneratorDifferentialSpec extends HibernateGormDatastoreSpec {
cascade : [cascadeActions(boundProperty.cascade), cascadeActions(annotatedProperty.cascade)],
insertable : [boundProperty.insertable, annotatedProperty.insertable],
updatable : [boundProperty.updateable, annotatedProperty.updateable],
+ propertyLazy : [boundProperty.lazy, annotatedProperty.lazy],
]
if (bound instanceof OneToOne) {
pairs.referencedProperty = [bound.referencedPropertyName, annotated.referencedPropertyName]
@@ -1641,6 +1642,9 @@ class GrailsDomainGeneratorDifferentialSpec extends HibernateGormDatastoreSpec {
return ["${where} columns: generator=${annotatedSelectables}${annotatedNullable} binder=${boundSelectables}${boundNullable}".toString()]
}
List<String> found = []
+ if (bound.lazy != annotated.lazy) {
+ found << "${where} propertyLazy: generator=${annotated.lazy} binder=${bound.lazy}".toString()
+ }
for (int i = 0; i < boundColumns.size(); i++) {
Column boundColumn = boundColumns[i]
Column annotatedColumn = annotatedColumns[i]
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/jpa/GrailsDomainGeneratorLazyPropertySpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/jpa/GrailsDomainGeneratorLazyPropertySpec.groovy
new file mode 100644
index 0000000000..260c17e0fd
--- /dev/null
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/jpa/GrailsDomainGeneratorLazyPropertySpec.groovy
@@ -0,0 +1,107 @@
+/*
+ * 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 org.grails.orm.hibernate.cfg.domainbinding.jpa
+
+import grails.gorm.annotation.Entity
+import jakarta.persistence.Basic
+import jakarta.persistence.FetchType
+import org.hibernate.boot.Metadata
+import org.hibernate.mapping.PersistentClass
+
+import org.grails.orm.hibernate.cfg.domainbinding.hibernate.GrailsHibernatePersistentEntity
+
+/**
+ * Describes how {@link GrailsDomainGenerator} states {@code lazy: true} on a simple or enum property: the binder marks
+ * the Hibernate property lazy (a lazy attribute, which Hibernate loads on first access when the class is enhanced), and
+ * the annotation for that is {@code @Basic(fetch = LAZY)}. Where the binder sets it and annotations cannot say it, the
+ * generator rejects the entity by name.
+ */
+class GrailsDomainGeneratorLazyPropertySpec extends GrailsDomainGeneratorSupport {
+
+ void setupSpec() {
+ manager.registerDomainClasses(GenLazyBasic, GenLazyEmbedded)
+ }
+
+ void "a lazy property is a basic attribute fetched lazily"() {
+ when:
+ Class<?> generated = generateGroup(GenLazyBasic).values().first()
+
+ then:
+ generated.getDeclaredField('big').getAnnotation(Basic).fetch() == FetchType.LAZY
+ generated.getDeclaredField('state').getAnnotation(Basic).fetch() == FetchType.LAZY
+ !generated.getDeclaredField('plain').isAnnotationPresent(Basic)
+ }
+
+ void "Hibernate's annotation binder reads the lazy properties as the binder bound them"() {
+ given:
+ Map<GrailsHibernatePersistentEntity, Class<?>> classes = generateGroup(GenLazyBasic)
+ Metadata metadata = annotationMetadata(classes.values())
+ PersistentClass bound = entity(GenLazyBasic).persistentClass
+ PersistentClass read = metadata.getEntityBinding(classes[entity(GenLazyBasic)].name)
+
+ expect:
+ ['plain', 'big', 'state'].every { String name -> bound.getProperty(name).lazy == read.getProperty(name).lazy }
+ bound.getProperty('big').lazy
+ }
+
+ void "lazy on an embedded property is rejected by name"() {
+ when:
+ generateGroup(GenLazyEmbedded)
+
+ then:
+ UnsupportedOperationException e = thrown()
+ e.message.contains('GenLazyEmbedded')
+ e.message.contains('home')
+ e.message.contains('lazy')
+ }
+}
+
+@Entity
+class GenLazyBasic {
+
+ String plain
+ String big
+ GenLazyState state
+
+ static mapping = {
+ big lazy: true
+ state lazy: true
+ }
+}
+
+@Entity
+class GenLazyEmbedded {
+
+ GenLazyHome home
+
+ static embedded = ['home']
+
+ static mapping = {
+ home lazy: true
+ }
+}
+
+class GenLazyHome {
+
+ String street
+}
+
+enum GenLazyState {
+ ON, OFF
+}