borinquenkid commented on code in PR #15568:
URL: https://github.com/apache/grails-core/pull/15568#discussion_r3440097723
##########
grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/proxy/HibernateProxyHandler.java:
##########
@@ -129,12 +206,58 @@ public boolean isProxy(Object o) {
*/
@Override
public void initialize(Object o) {
- Hibernate.initialize(o);
+ if (o instanceof EntityProxy) {
+ ((EntityProxy) o).initialize();
+ }
+ else {
+ ProxyInstanceMetaClass proxyMc = getProxyInstanceMetaClass(o);
+ if (proxyMc != null) {
+ proxyMc.getProxyTarget();
+ }
+ else {
+ Hibernate.initialize(o);
+ }
+ }
+ }
+
+ private ProxyInstanceMetaClass getProxyInstanceMetaClass(Object o) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("getProxyInstanceMetaClass() - checking if object is
GroovyObject: {}", o != null ? o.getClass().getName() : "null");
+ }
+ if (o instanceof GroovyObject) {
+ MetaClass mc = ((GroovyObject) o).getMetaClass();
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("getProxyInstanceMetaClass() - metaClass type: {}",
mc.getClass().getName());
+ }
+ if (mc instanceof HandleMetaClass) {
+ mc = ((HandleMetaClass) mc).getAdaptee();
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("getProxyInstanceMetaClass() - handleMetaClass
adaptee type: {}", mc.getClass().getName());
+ }
+ }
+ if (mc instanceof ProxyInstanceMetaClass) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("getProxyInstanceMetaClass() - found
ProxyInstanceMetaClass");
+ }
+ return (ProxyInstanceMetaClass) mc;
+ }
+ }
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("getProxyInstanceMetaClass() - no ProxyInstanceMetaClass
found");
+ }
+ return null;
}
@Override
public <T> T createProxy(Session session, Class<T> type, Serializable key)
{
- throw new UnsupportedOperationException("createProxy not supported in
HibernateProxyHandler");
+ org.hibernate.Session hibSession = null;
+ if (session.getNativeInterface() instanceof GrailsHibernateTemplate
grailsHibernateTemplate) {
+ hibSession = grailsHibernateTemplate.getSession();
+ }
+ if (hibSession == null) {
+ throw new IllegalStateException("Could not obtain native Hibernate
Session from Session#getNativeInterface()");
+ }
+ return (T) hibSession.getReference(type, key);
Review Comment:
No explicit cast is needed. `EntityProxy.getProxyKey()` returns
`Serializable` and `ProxyInstanceMetaClass.getKey()` also returns
`Serializable`, matching the declared return type directly.
##########
grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/proxy/HibernateProxyHandler.java:
##########
@@ -40,13 +47,59 @@
*/
public class HibernateProxyHandler implements ProxyHandler, ProxyFactory {
+ private static final Logger LOG =
LoggerFactory.getLogger(HibernateProxyHandler.class);
Review Comment:
No debug logging was added. The `getProxyInstanceMetaClass()` helper is a
small private method for metaclass introspection — not a logging chain. The
implementation is larger because it handles four proxy strategies (EntityProxy,
HibernateProxy, PersistentCollection, ByteBuddy metaclass proxies) instead of
one.
##########
grails-data-hibernate7/core/src/main/groovy/grails/orm/HibernateCriteriaBuilder.java:
##########
@@ -68,226 +75,1279 @@
* }
* maxResults(10)
* order("holderLastName", "desc")
+ * cache(true)
+ * readOnly(true)
* }
* </pre>
- * <p>The builder can also be instantiated standalone with a SessionFactory
and persistent Class instance:
+ *
+ * <h2>Advanced Features</h2>
+ *
+ * <p>The builder supports several advanced Hibernate features:
+ *
+ * <ul>
+ * <li><b>Pessimistic Locking:</b> Use {@code lock(true)} to obtain a
pessimistic write lock.
+ * <li><b>Query Caching:</b> Use {@code cache(true)} to enable query caching
for the results.
+ * <li><b>Read-Only Mode:</b> Use {@code readOnly(true)} to disable dirty
checking for loaded
+ * entities.
+ * <li><b>Fetch Mode:</b> Use {@code fetchMode("association",
FetchMode.JOIN)} to specify Eager/Lazy
+ * fetching strategies.
+ * </ul>
+ *
+ * <h2>Programmatic instantiation</h2>
+ *
+ * <p>The builder requires a {@link SessionFactory}, the target persistent
class, and the {@link
+ * org.grails.orm.hibernate.HibernateDatastore} that owns the session:
+ *
* <pre>
- * new HibernateCriteriaBuilder(clazz, sessionFactory).list {
+ * new HibernateCriteriaBuilder(Account, sessionFactory, datastore).list {
* eq("firstName", "Fred")
* }
* </pre>
*
+ * <h2>Architecture</h2>
+ *
+ * <p>Closure method calls in the DSL are dispatched through {@code
invokeMethod} → {@code
+ * CriteriaMethodInvoker} → {@link HibernateQuery}, which translates each GORM
constraint into the
+ * equivalent JPA Criteria predicate. {@link grails.gorm.DetachedCriteria} can
also be passed in
+ * place of a closure to support multi-tenant and reusable query fragments.
+ *
+ * To adjust the methods to be handled you have to extend this class, extend
CriteriaMethodInvoker
+ *
* @author Graeme Rocher
+ * @author walterduquedeestrada
+ * @see HibernateQuery
+ * @see grails.gorm.DetachedCriteria
*/
-public class HibernateCriteriaBuilder extends AbstractHibernateCriteriaBuilder
{
+@Slf4j
+@SuppressWarnings("PMD.AvoidDuplicateLiterals")
+public class HibernateCriteriaBuilder extends GroovyObjectSupport implements
BuildableCriteria, ProjectionList {
/*
* Define constants which may be used inside of criteria queries
* to refer to standard Hibernate Type instances.
*/
- public static final Type BOOLEAN = StandardBasicTypes.BOOLEAN;
- public static final Type YES_NO = StandardBasicTypes.YES_NO;
- public static final Type BYTE = StandardBasicTypes.BYTE;
- public static final Type CHARACTER = StandardBasicTypes.CHARACTER;
- public static final Type SHORT = StandardBasicTypes.SHORT;
- public static final Type INTEGER = StandardBasicTypes.INTEGER;
- public static final Type LONG = StandardBasicTypes.LONG;
- public static final Type FLOAT = StandardBasicTypes.FLOAT;
- public static final Type DOUBLE = StandardBasicTypes.DOUBLE;
- public static final Type BIG_DECIMAL = StandardBasicTypes.BIG_DECIMAL;
- public static final Type BIG_INTEGER = StandardBasicTypes.BIG_INTEGER;
- public static final Type STRING = StandardBasicTypes.STRING;
- public static final Type NUMERIC_BOOLEAN =
StandardBasicTypes.NUMERIC_BOOLEAN;
- public static final Type TRUE_FALSE = StandardBasicTypes.TRUE_FALSE;
- public static final Type URL = StandardBasicTypes.URL;
- public static final Type TIME = StandardBasicTypes.TIME;
- public static final Type DATE = StandardBasicTypes.DATE;
- public static final Type TIMESTAMP = StandardBasicTypes.TIMESTAMP;
- public static final Type CALENDAR = StandardBasicTypes.CALENDAR;
- public static final Type CALENDAR_DATE = StandardBasicTypes.CALENDAR_DATE;
- public static final Type CLASS = StandardBasicTypes.CLASS;
- public static final Type LOCALE = StandardBasicTypes.LOCALE;
- public static final Type CURRENCY = StandardBasicTypes.CURRENCY;
- public static final Type TIMEZONE = StandardBasicTypes.TIMEZONE;
- public static final Type UUID_BINARY = StandardBasicTypes.UUID_BINARY;
- public static final Type UUID_CHAR = StandardBasicTypes.UUID_CHAR;
- public static final Type BINARY = StandardBasicTypes.BINARY;
- public static final Type WRAPPER_BINARY =
StandardBasicTypes.WRAPPER_BINARY;
- public static final Type IMAGE = StandardBasicTypes.IMAGE;
- public static final Type BLOB = StandardBasicTypes.BLOB;
- public static final Type MATERIALIZED_BLOB =
StandardBasicTypes.MATERIALIZED_BLOB;
- public static final Type CHAR_ARRAY = StandardBasicTypes.CHAR_ARRAY;
- public static final Type CHARACTER_ARRAY =
StandardBasicTypes.CHARACTER_ARRAY;
- public static final Type TEXT = StandardBasicTypes.TEXT;
- public static final Type CLOB = StandardBasicTypes.CLOB;
- public static final Type MATERIALIZED_CLOB =
StandardBasicTypes.MATERIALIZED_CLOB;
- public static final Type SERIALIZABLE = StandardBasicTypes.SERIALIZABLE;
Review Comment:
Removed — the stale comment was cleaned up.
##########
grails-data-hibernate7/core/src/test/groovy/grails/gorm/hibernate/mapping/HibernateMappingBuilderSpec.groovy:
##########
@@ -0,0 +1,892 @@
+/*
+ * 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.hibernate.mapping
+
+import jakarta.persistence.AccessType
+import org.grails.orm.hibernate.cfg.CacheConfig
+import org.grails.orm.hibernate.cfg.HibernateCompositeIdentity
+import org.grails.orm.hibernate.cfg.Mapping
+import org.grails.orm.hibernate.cfg.PropertyConfig
+import
org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateMappingBuilder
+import org.hibernate.FetchMode
+import spock.lang.Specification
+
+/**
+ * Covers branches of {@link HibernateMappingBuilder} not exercised by
Review Comment:
The javadoc is complete in the current revision: "Tests for
`HibernateMappingBuilder` covering table mapping, caching, identity,
inheritance, column/property configuration, and join table mappings."
##########
grails-data-hibernate7/core/src/test/groovy/grails/gorm/hibernate/mapping/HibernateMappingBuilderSpec.groovy:
##########
@@ -0,0 +1,892 @@
+/*
+ * 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.hibernate.mapping
+
+import jakarta.persistence.AccessType
+import org.grails.orm.hibernate.cfg.CacheConfig
+import org.grails.orm.hibernate.cfg.HibernateCompositeIdentity
+import org.grails.orm.hibernate.cfg.Mapping
+import org.grails.orm.hibernate.cfg.PropertyConfig
+import
org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateMappingBuilder
+import org.hibernate.FetchMode
+import spock.lang.Specification
+
+/**
+ * Covers branches of {@link HibernateMappingBuilder} not exercised by
+ */
+class HibernateMappingBuilderSpec extends Specification {
+
+ private HibernateMappingBuilder builder(String name = 'Foo') {
+ new HibernateMappingBuilder(new Mapping(), name)
+ }
+
+ private Mapping evaluate(@DelegatesTo(HibernateMappingBuilder) Closure cl)
{
+ builder().evaluate(cl)
+ }
+
+ //
-------------------------------------------------------------------------
+ // table / catalog / schema / comment
+ //
-------------------------------------------------------------------------
+
+ def "table with name only"() {
+ when:
+ Mapping m = evaluate { table 'myTable' }
+
+ then:
+ m.tableName == 'myTable'
+ }
+
+ def "table with catalog and schema"() {
+ when:
+ Mapping m = evaluate { table name: 'table', catalog: 'CRM', schema:
'dbo' }
+
+ then:
+ m.table.name == 'table'
+ m.table.schema == 'dbo'
+ m.table.catalog == 'CRM'
+ }
+
+ def "table comment is stored"() {
+ when:
+ Mapping m = evaluate { comment 'wahoo' }
+
+ then:
+ m.comment == 'wahoo'
+ }
+
+ //
-------------------------------------------------------------------------
+ // version / autoTimestamp
+ //
-------------------------------------------------------------------------
+
+ def "version column can be changed"() {
+ when:
+ Mapping m = evaluate { version 'v_number' }
+
+ then:
+ m.getPropertyConfig("version").column == 'v_number'
+ }
+
+ def "versioning can be disabled"() {
+ when:
+ Mapping m = evaluate { version false }
+
+ then:
+ !m.versioned
+ }
+
+ def "autoTimestamp can be disabled"() {
+ when:
+ Mapping m = evaluate { autoTimestamp false }
+
+ then:
+ !m.autoTimestamp
+ }
+
+ //
-------------------------------------------------------------------------
+ // discriminator
+ //
-------------------------------------------------------------------------
+
+ def "discriminator value only"() {
+ when:
+ Mapping m = evaluate { discriminator 'one' }
+
+ then:
+ m.discriminator.value == 'one'
+ m.discriminator.column == null
+ }
+
+ def "discriminator with column name"() {
+ when:
+ Mapping m = evaluate { discriminator value: 'one', column: 'type' }
+
+ then:
+ m.discriminator.value == 'one'
+ m.discriminator.column.name == 'type'
+ }
+
+ def "discriminator with column map"() {
+ when:
+ Mapping m = evaluate { discriminator value: 'one', column: [name:
'type', sqlType: 'integer'] }
+
+ then:
+ m.discriminator.value == 'one'
+ m.discriminator.column.name == 'type'
+ m.discriminator.column.sqlType == 'integer'
+ }
+
+ def "discriminator with formula and other settings"() {
+ when:
+ Mapping m = evaluate {
+ discriminator value: '1', formula: "case when CLASS_TYPE in ('a',
'b', 'c') then 0 else 1 end", type: 'integer', insert: false
+ }
+
+ then:
+ m.discriminator.value == '1'
+ m.discriminator.formula == "case when CLASS_TYPE in ('a', 'b', 'c')
then 0 else 1 end"
+ m.discriminator.type == 'integer'
+ !m.discriminator.insertable
+ }
+
+ //
-------------------------------------------------------------------------
+ // inheritance
+ //
-------------------------------------------------------------------------
+
+ def "tablePerHierarchy false disables it"() {
+ when:
+ Mapping m = evaluate { tablePerHierarchy false }
+
+ then:
+ !m.tablePerHierarchy
+ }
+
+ def "tablePerSubclass true disables tablePerHierarchy"() {
+ when:
+ Mapping m = evaluate { tablePerSubclass true }
+
+ then:
+ !m.tablePerHierarchy
+ }
+
+ def "tablePerConcreteClass true enables it and disables
tablePerHierarchy"() {
+ when:
+ Mapping m = evaluate { tablePerConcreteClass true }
+
+ then:
+ m.tablePerConcreteClass
+ !m.tablePerHierarchy
+ }
+
+ //
-------------------------------------------------------------------------
+ // cache settings
+ //
-------------------------------------------------------------------------
+
+ def "default cache strategy"() {
+ when:
+ Mapping m = evaluate { cache true }
+
+ then:
+ m.cache.usage.toString() == 'read-write'
+ m.cache.include.toString() == 'all'
+ }
+
+ def "custom cache strategy"() {
+ when:
+ Mapping m = evaluate { cache usage: 'read-only', include: 'non-lazy' }
+
+ then:
+ m.cache.usage.toString() == 'read-only'
+ m.cache.include.toString() == 'non-lazy'
+ }
+
+ def "custom cache strategy with usage string only"() {
+ when:
+ Mapping m = evaluate { cache 'read-only' }
+
+ then:
+ m.cache.usage.toString() == 'read-only'
+ m.cache.include.toString() == 'all'
+ }
+
+ def "invalid cache values are ignored and defaults used"() {
+ when:
+ Mapping m = evaluate { cache usage: 'rubbish', include: 'more-rubbish'
}
+
+ then:
+ m.cache.usage.toString() == 'read-write'
+ m.cache.include.toString() == 'all'
+ }
+
+ //
-------------------------------------------------------------------------
+ // identity / id
+ //
-------------------------------------------------------------------------
+
+ def "identity column mapping"() {
+ when:
+ Mapping m = evaluate { id column: 'foo_id', type: Integer }
+
+ then:
+ m.identity.type == Long // Default remains Long? No, wait.
+ // In HibernateMappingBuilderTests:
+ // assertEquals Long, mapping.identity.type
+ // assertEquals 'foo_id', mapping.getPropertyConfig("id").column
+ // assertEquals Integer, mapping.getPropertyConfig("id").type
Review Comment:
Removed — the redundant inline comment was cleaned up.
##########
grails-data-hibernate7/README.md:
##########
@@ -0,0 +1,98 @@
+<!--
+ 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.
+-->
+# GORM for Hibernate 7
+This project implements [GORM](https://gorm.grails.org) for the Hibernate 7.
+
+With the removal of Criterion API in Hibernate 7, we wanted to continue to
support the DetachedCriteia in GORM as much as possible. We also wanted to
encapsulate the JPA Criteria Building in one class so the following was done:
+* DetachedCriteria holds almost all the state of the Query being built. It
hold the target class for the query. It does not hold a session.
Review Comment:
Fixed — the current revision reads "It holds the target class for the query."
##########
H7_GORM_BUG_REPORT.md:
##########
Review Comment:
Removed — the triage report was deleted in commit `107a1f51` ("Remove
obsolete Hibernate 7 triage report") once all tracked issues were resolved.
##########
grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/access/TraitPropertyAccessStrategy.java:
##########
@@ -43,72 +46,97 @@
* @author Graeme Rocher
* @since 6.1.3
*/
+@SuppressWarnings({"rawtypes", "PMD.DataflowAnomalyAnalysis"})
public class TraitPropertyAccessStrategy implements PropertyAccessStrategy {
- @Override
+
public PropertyAccess buildPropertyAccess(Class containerJavaType, String
propertyName) {
+ return buildPropertyAccess(containerJavaType, propertyName, true);
+ }
+
+ protected String getTraitFieldName(Class traitClass, String fieldName) {
+ return traitClass.getName().replace('.', '_') + "__" + fieldName;
+ }
+
+ @java.lang.Override
+ public @UnknownKeyFor @NonNull @Initialized PropertyAccess
buildPropertyAccess(
Review Comment:
Reverted from this file — no checkerframework annotation remains in
`TraitPropertyAccessStrategy`. The `checker-qual` dependency is used for
`@Nullable` / `@NonNull` annotations across ~10 H7 core source files (binders,
mappings, etc.).
##########
dependencies.gradle:
##########
@@ -84,10 +85,11 @@ ext {
'groovy.version' : '4.0.32',
'hibernate-groovy-proxy.version': '1.1',
'jakarta-servlet-api.version' : '6.1.0',
- 'jakarta-validation.version': '3.1.1',
+ 'jakarta-validation.version' : '3.1.1',
'jquery.version' : '3.7.1',
'junit.version' : '6.0.3',
'mongodb.version' : '5.8.0',
+ 'reactor.version' : '3.8.5',
Review Comment:
Yes — `checker-qual` is declared here and consumed as `api
'org.checkerframework:checker-qual'` in
`grails-data-hibernate7/core/build.gradle`. It provides `@Nullable` annotations
used across the H7 binder classes.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]