This is an automated email from the ASF dual-hosted git repository. borinquenkid pushed a commit to branch 8.0.x-hibernate7-dev in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit 1447a0b4dc0ef669b9477453c1fb2e42e065561c Author: Walter Duque de Estrada <[email protected]> AuthorDate: Mon Mar 23 09:40:55 2026 -0500 hibernate 7: * Solidify PagedResultList --- .../orm/hibernate/HibernateGormStaticApi.groovy | 4 +- .../proxy/ByteBuddyGroovyInterceptor.java | 12 +-- .../orm/hibernate/query/HibernateHqlQuery.java | 31 ++++--- .../orm/hibernate/query/HqlQueryContext.java | 31 ++++--- .../orm/hibernate/query/PagedResultList.java | 2 +- .../grails/gorm/specs/PagedResultListSpec.groovy | 97 ++++++++++++++++++++++ .../main/groovy/grails/gorm/PagedResultList.java | 18 ++++ .../org/grails/datastore/mapping/query/Query.java | 14 ++++ 8 files changed, 174 insertions(+), 35 deletions(-) diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/HibernateGormStaticApi.groovy b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/HibernateGormStaticApi.groovy index 4be8cf0e90..dc9ee4c078 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/HibernateGormStaticApi.groovy +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/HibernateGormStaticApi.groovy @@ -391,7 +391,7 @@ class HibernateGormStaticApi<D> extends GormStaticApi<D> { @SuppressWarnings('GroovyAssignabilityCheck') private HibernateHqlQuery prepareHqlQuery(CharSequence hql, boolean isNative, boolean isUpdate, - Map namedParams, Collection positionalParams, Map querySettings) { + Map<String,Object> namedParams, Collection<Object> positionalParams, Map<String,Object> querySettings) { def ctx = HqlQueryContext.prepare(persistentEntity, hql, namedParams, positionalParams, querySettings, isNative, isUpdate) return HibernateHqlQuery.createHqlQuery( (HibernateDatastore) datastore, @@ -441,7 +441,7 @@ class HibernateGormStaticApi<D> extends GormStaticApi<D> { sessionFactory, persistentEntity, ctx, - getHibernateTemplate(), + getHibernateTemplate(),Ï datastore.mappingContext.conversionService ) if (params.containsKey('max')) { diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/proxy/ByteBuddyGroovyInterceptor.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/proxy/ByteBuddyGroovyInterceptor.java index 27c9d561aa..7802b054e5 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/proxy/ByteBuddyGroovyInterceptor.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/proxy/ByteBuddyGroovyInterceptor.java @@ -63,8 +63,7 @@ public class ByteBuddyGroovyInterceptor extends ByteBuddyInterceptor { @Override public Object intercept(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); - System.out.println("Intercepting method: " + methodName + " on proxy: " + getEntityName() + ":" - + getIdentifier() + " (Uninitialized: " + isUninitialized() + ")"); + // Check these BEFORE calling this.invoke() to avoid premature initialization in Hibernate 7 if ((getIdentifierMethod != null && methodName.equals(getIdentifierMethod.getName())) @@ -79,27 +78,22 @@ public class ByteBuddyGroovyInterceptor extends ByteBuddyInterceptor { getEntityName(), getPersistentClass(), getIdentifier()); Object result = GroovyProxyInterceptorLogic.handleUninitialized(state, methodName, args); if (result != GroovyProxyInterceptorLogic.INVOKE_IMPLEMENTATION) { - System.out.println("Handled uninitialized access for: " + methodName); return result; } } - System.out.println("Delegating to Hibernate invoke for: " + methodName); final Object result = this.invoke(method, args, proxy); if (result != INVOKE_IMPLEMENTATION) { return result; } if (GroovyProxyInterceptorLogic.isGroovyMethod(methodName)) { - System.out.println("Handling Groovy method: " + methodName); final Object target = getImplementation(); try { - if (isPublic(getPersistentClass(), method)) { - return method.invoke(target, args); - } else { + if (!isPublic(getPersistentClass(), method)) { method.setAccessible(true); - return method.invoke(target, args); } + return method.invoke(target, args); } catch (InvocationTargetException ite) { throw ite.getTargetException(); } diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/HibernateHqlQuery.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/HibernateHqlQuery.java index 3683801770..9c29a7ba3d 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/HibernateHqlQuery.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/HibernateHqlQuery.java @@ -186,9 +186,16 @@ public class HibernateHqlQuery extends Query { } protected void populateQuerySettings(Map<?, ?> args, ConversionService conversionService) { - ifPresent(args, HibernateQueryArgument.MAX.value(), v -> delegate.setMaxResults(toInt(v, conversionService))); - ifPresent( - args, HibernateQueryArgument.OFFSET.value(), v -> delegate.setFirstResult(toInt(v, conversionService))); + ifPresent(args, HibernateQueryArgument.MAX.value(), v -> { + int max = toInt(v, conversionService); + delegate.setMaxResults(max); + max(max); + }); + ifPresent(args, HibernateQueryArgument.OFFSET.value(), v -> { + int offset = toInt(v, conversionService); + delegate.setFirstResult(offset); + offset(offset); + }); ifPresent(args, HibernateQueryArgument.CACHE.value(), v -> delegate.setCacheable(toBool(v))); ifPresent( args, @@ -243,14 +250,7 @@ public class HibernateHqlQuery extends Query { throw new GrailsQueryException("Named parameter's name must be a String: " + namedArgs); } String name = key.toString(); - if (HibernateQueryArgument.MAX.value().equals(name) - || HibernateQueryArgument.OFFSET.value().equals(name) - || HibernateQueryArgument.CACHE.value().equals(name) - || HibernateQueryArgument.FETCH_SIZE.value().equals(name) - || HibernateQueryArgument.TIMEOUT.value().equals(name) - || HibernateQueryArgument.READ_ONLY.value().equals(name) - || HibernateQueryArgument.FLUSH_MODE.value().equals(name) - || HibernateQueryArgument.LOCK.value().equals(name)) { + if (isGormArgument(name)) { return; } if (value == null) { @@ -267,6 +267,15 @@ public class HibernateHqlQuery extends Query { }); } + private static boolean isGormArgument(String name) { + for (HibernateQueryArgument arg : HibernateQueryArgument.values()) { + if (arg.value().equals(name)) { + return true; + } + } + return false; + } + protected void populateQueryWithIndexedArguments(List<?> params) { if (params == null) return; for (int i = 0; i < params.size(); i++) { diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/HqlQueryContext.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/HqlQueryContext.java index 5fbf29c98f..de1c0fcc5e 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/HqlQueryContext.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/HqlQueryContext.java @@ -21,6 +21,7 @@ package org.grails.orm.hibernate.query; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; @@ -54,7 +55,7 @@ public record HqlQueryContext( String hql, Class<?> targetClass, Map<String, Object> namedParams, - Collection<?> positionalParams, + List<Object> positionalParams, Map<String, Object> querySettings, boolean isUpdate, boolean isNative) { @@ -65,33 +66,35 @@ public record HqlQueryContext( * Resolves the final HQL string, the result target class, and expands any {@link GString} into * named parameters. No {@code Session} is required. */ - @SuppressWarnings("unchecked") public static HqlQueryContext prepare( PersistentEntity entity, CharSequence queryCharseq, - Map<?, ?> namedParams, - Collection<?> positionalParams, - Map<String, Object> querySettings, + Map<String,Object> namedParams, + Collection<Object> positionalParams, + Map<String,Object> querySettings, boolean isNative, boolean isUpdate) { Map<String, Object> _namedParams = - namedParams != null ? new HashMap<>((Map<String, Object>) namedParams) : new HashMap<>(); - Collection<Object> positionalParamsCopy = positionalParams != null ? new ArrayList<>(positionalParams) : null; - Map<String, Object> querySettingsCopy = querySettings != null ? new HashMap<>(querySettings) : null; + namedParams != null ? new HashMap<>(namedParams) : new HashMap<>(); + List<Object> positionalParamsCopy = positionalParams != null ? new ArrayList<>(positionalParams) : new ArrayList<>(); + Map<String, Object> querySettingsCopy = querySettings != null ? new HashMap<>(querySettings) : new HashMap<>(); + + boolean _isNative = toBool(isNative); + boolean _isUpdate = toBool(isUpdate); String hql; // Prefer positional resolution only if positional parameters are explicitly provided (not null) // and named parameters are empty. This preserves legacy GString->named parameter behavior // while allowing opt-in to positional parameters via methods that pass them. - if (positionalParamsCopy != null && _namedParams.isEmpty()) { - hql = resolveHql(queryCharseq, isNative, positionalParamsCopy); + if (_namedParams.isEmpty()) { + hql = resolveHql(queryCharseq, _isNative, positionalParamsCopy); } else { - hql = resolveHql(queryCharseq, isNative, _namedParams); + hql = resolveHql(queryCharseq, _isNative, _namedParams); } Class<?> target = getTarget(hql, entity.getJavaClass()); return new HqlQueryContext( - hql, target, _namedParams, positionalParamsCopy, querySettingsCopy, isUpdate, isNative); + hql, target, _namedParams, positionalParamsCopy, querySettingsCopy, _isUpdate, _isNative); } // ─── HQL resolution ────────────────────────────────────────────────────── @@ -339,4 +342,8 @@ public record HqlQueryContext( } return sql.toString(); } + + private static boolean toBool(Object v) { + return v instanceof Boolean b ? b : v != null && Boolean.parseBoolean(v.toString()); + } } diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/PagedResultList.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/PagedResultList.java index 0283ae881b..55926d5b3d 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/PagedResultList.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/PagedResultList.java @@ -31,7 +31,7 @@ import org.hibernate.query.Query; import org.grails.datastore.mapping.model.PersistentEntity; import org.grails.orm.hibernate.GrailsHibernateTemplate; -public class PagedResultList extends grails.gorm.PagedResultList { +public class PagedResultList<E> extends grails.gorm.PagedResultList<E> { private static final long serialVersionUID = 1L; diff --git a/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/PagedResultListSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/PagedResultListSpec.groovy new file mode 100644 index 0000000000..7175246be1 --- /dev/null +++ b/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/PagedResultListSpec.groovy @@ -0,0 +1,97 @@ +/* + * 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.specs + +import grails.gorm.annotation.Entity +import grails.gorm.hibernate.HibernateEntity +import org.grails.orm.hibernate.query.PagedResultList + +class PagedResultListSpec extends HibernateGormDatastoreSpec { + + void setupSpec() { + manager.addAllDomainClasses([PRLBook]) + } + + void "test PagedResultList totalCount with HQL query"() { + given: + new PRLBook(title: "The Stand").save() + new PRLBook(title: "The Shining").save() + new PRLBook(title: "Carrie").save() + session.flush() + session.clear() + + when: + def results = PRLBook.list(max: 2, sort: "title") + + then: + results instanceof PagedResultList + results.size() == 2 + results.totalCount == 3 + results[0].title == "Carrie" + results[1].title == "The Shining" + } + + void "test PagedResultList with offset and max"() { + given: + (1..10).each { i -> new PRLBook(title: "Book $i").save() } + session.flush() + session.clear() + + when: + def results = PRLBook.list(max: 3, offset: 2, sort: "id") + + then: + results instanceof PagedResultList + results.size() == 3 + results.totalCount == 10 + results.max == 3 + results.offset == 2 + // results[0] should be "Book 3" (offset 2, 0-indexed id assumed here for simplicity of logic) + results.every { it.title.startsWith("Book ") } + } + + void "test PagedResultList totalCount with Criteria query"() { + given: + new PRLBook(title: "The Stand").save() + new PRLBook(title: "The Shining").save() + new PRLBook(title: "Carrie").save() + session.flush() + session.clear() + + when: + def results = PRLBook.createCriteria().list(max: 2) { + like("title", "The %") + order("title") + } + + then: + results instanceof grails.gorm.PagedResultList + results.size() == 2 + results.totalCount == 2 + results.max == 2 + results.offset == 0 + results[0].title == "The Shining" + results[1].title == "The Stand" + } +} + +@Entity +class PRLBook implements HibernateEntity<PRLBook> { + Long id + String title +} diff --git a/grails-datamapping-core/src/main/groovy/grails/gorm/PagedResultList.java b/grails-datamapping-core/src/main/groovy/grails/gorm/PagedResultList.java index eeb18a1286..aae01602ed 100644 --- a/grails-datamapping-core/src/main/groovy/grails/gorm/PagedResultList.java +++ b/grails-datamapping-core/src/main/groovy/grails/gorm/PagedResultList.java @@ -58,6 +58,24 @@ public class PagedResultList<E> implements Serializable, List<E> { return totalCount; } + /** + * @return The maximum number of results to return or null if not specified + */ + public Integer getMax() { + return query != null ? query.getMax() : null; + } + + /** + * @return The offset of the first result or 0 if not specified + */ + public int getOffset() { + if (query != null) { + Integer offset = query.getOffset(); + return offset != null ? offset : 0; + } + return 0; + } + @Override public E get(int i) { return resultList.get(i); diff --git a/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/query/Query.java b/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/query/Query.java index 3d3e705974..a778e2e020 100644 --- a/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/query/Query.java +++ b/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/query/Query.java @@ -87,6 +87,20 @@ public abstract class Query implements Cloneable { return newQuery; } + /** + * @return The maximum number of results to return + */ + public Integer getMax() { + return max; + } + + /** + * @return The offset of the first result + */ + public Integer getOffset() { + return offset; + } + /** * @return The criteria defined by this query */
