From 5ec8d289218f371608e7347a8526da0ccd38c563 Mon Sep 17 00:00:00 2001 From: estradaw Date: Thu, 16 Jan 2025 13:36:00 -0600 Subject: [PATCH] partial support of gorm DetachedCriteria --- .../grails/orm/HibernateCriteriaBuilder.java | 5 +- .../AbstractHibernateGormStaticApi.groovy | 22 +- .../hibernate/HibernateGormStaticApi.groovy | 9 +- .../AbstractHibernateCriteriaBuilder.java | 259 ++++++++++-------- .../query/AbstractHibernateQuery.java | 130 ++------- .../DetachedCriteriaProjectionSpec.groovy | 15 +- 6 files changed, 188 insertions(+), 252 deletions(-) diff --git a/grails-datastore-gorm-hibernate/src/main/groovy/grails/orm/HibernateCriteriaBuilder.java b/grails-datastore-gorm-hibernate/src/main/groovy/grails/orm/HibernateCriteriaBuilder.java index 0fb41ca3..110563d6 100644 --- a/grails-datastore-gorm-hibernate/src/main/groovy/grails/orm/HibernateCriteriaBuilder.java +++ b/grails-datastore-gorm-hibernate/src/main/groovy/grails/orm/HibernateCriteriaBuilder.java @@ -25,10 +25,11 @@ import org.hibernate.SessionFactory; import org.hibernate.type.BasicTypeReference; import org.hibernate.type.StandardBasicTypes; -import org.hibernate.type.Type; + import org.springframework.orm.hibernate5.SessionHolder; import org.springframework.transaction.support.TransactionSynchronizationManager; + import java.io.Serializable; import java.math.BigDecimal; import java.math.BigInteger; @@ -128,8 +129,6 @@ public HibernateCriteriaBuilder(Class targetClass, SessionFactory sessionFactory - - protected Class getClassForAssociationType(Attribute type) { if (type instanceof PluralAttribute) { return ((PluralAttribute)type).getElementType().getJavaType(); diff --git a/grails-datastore-gorm-hibernate/src/main/groovy/org/grails/orm/hibernate/AbstractHibernateGormStaticApi.groovy b/grails-datastore-gorm-hibernate/src/main/groovy/org/grails/orm/hibernate/AbstractHibernateGormStaticApi.groovy index 9c4f5ac7..b3196bde 100644 --- a/grails-datastore-gorm-hibernate/src/main/groovy/org/grails/orm/hibernate/AbstractHibernateGormStaticApi.groovy +++ b/grails-datastore-gorm-hibernate/src/main/groovy/org/grails/orm/hibernate/AbstractHibernateGormStaticApi.groovy @@ -2,6 +2,8 @@ package org.grails.orm.hibernate import groovy.transform.CompileDynamic import groovy.transform.CompileStatic +import groovy.util.logging.Slf4j +import jakarta.persistence.NoResultException import org.grails.datastore.mapping.reflect.ClassUtils import org.grails.orm.hibernate.cfg.AbstractGrailsDomainBinder import org.grails.orm.hibernate.cfg.CompositeIdentity @@ -13,6 +15,7 @@ import org.grails.datastore.gorm.GormStaticApi import org.grails.datastore.gorm.finders.DynamicFinder import org.grails.datastore.gorm.finders.FinderMethod import org.hibernate.FlushMode +import org.hibernate.NonUniqueResultException import org.hibernate.Session import org.hibernate.jpa.QueryHints import org.hibernate.query.NativeQuery @@ -32,6 +35,7 @@ import jakarta.persistence.criteria.Root * @author Graeme Rocher * @since 4.0 */ +@Slf4j @CompileStatic abstract class AbstractHibernateGormStaticApi extends GormStaticApi { @@ -176,17 +180,15 @@ abstract class AbstractHibernateGormStaticApi extends GormStaticApi { CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Long.class) criteriaQuery.select(criteriaBuilder.count(criteriaQuery.from(persistentEntity.javaClass))) Query criteria = session.createQuery(criteriaQuery) - HibernateHqlQuery hibernateHqlQuery = new HibernateHqlQuery( - hibernateSession, persistentEntity, criteria) { - @Override - protected void flushBeforeQuery() { - // no-op - } + Long result =0 + try { + result = criteria.singleResult as Long + } catch (NonUniqueResultException nonUniqueResultException) { + log.warn(nonUniqueResultException.toString()) + } catch (NoResultException noResultException) { + log.warn(noResultException.toString()) } - hibernateTemplate.applySettings(criteria) - def result = hibernateHqlQuery.singleResult() - Number num = result == null ? 0 : (Number)result - return num + return result }) } diff --git a/grails-datastore-gorm-hibernate/src/main/groovy/org/grails/orm/hibernate/HibernateGormStaticApi.groovy b/grails-datastore-gorm-hibernate/src/main/groovy/org/grails/orm/hibernate/HibernateGormStaticApi.groovy index e87dccae..e71e51a1 100644 --- a/grails-datastore-gorm-hibernate/src/main/groovy/org/grails/orm/hibernate/HibernateGormStaticApi.groovy +++ b/grails-datastore-gorm-hibernate/src/main/groovy/org/grails/orm/hibernate/HibernateGormStaticApi.groovy @@ -130,11 +130,10 @@ class HibernateGormStaticApi extends AbstractHibernateGormStaticApi { @Override GrailsCriteria createCriteria() { - return null; -// def builder = new HibernateCriteriaBuilder(persistentClass, sessionFactory) -// builder.datastore = (AbstractHibernateDatastore)datastore -// builder.conversionService = conversionService -// return builder + def builder = new HibernateCriteriaBuilder(persistentClass, sessionFactory) + builder.datastore = (AbstractHibernateDatastore)datastore + builder.conversionService = conversionService + return builder } @Override diff --git a/grails-datastore-gorm-hibernate/src/main/groovy/org/grails/orm/hibernate/query/AbstractHibernateCriteriaBuilder.java b/grails-datastore-gorm-hibernate/src/main/groovy/org/grails/orm/hibernate/query/AbstractHibernateCriteriaBuilder.java index 682dcff5..0f731990 100644 --- a/grails-datastore-gorm-hibernate/src/main/groovy/org/grails/orm/hibernate/query/AbstractHibernateCriteriaBuilder.java +++ b/grails-datastore-gorm-hibernate/src/main/groovy/org/grails/orm/hibernate/query/AbstractHibernateCriteriaBuilder.java @@ -14,6 +14,7 @@ import jakarta.persistence.metamodel.EntityType; import org.grails.datastore.mapping.multitenancy.MultiTenancySettings; import org.grails.datastore.mapping.query.Query; +import org.grails.datastore.mapping.query.Restrictions; import org.grails.datastore.mapping.query.api.BuildableCriteria; import org.grails.datastore.mapping.query.api.Criteria; import org.grails.datastore.mapping.query.api.QueryableCriteria; @@ -90,6 +91,7 @@ public abstract class AbstractHibernateCriteriaBuilder extends GroovyObjectSuppo protected SessionFactory sessionFactory; protected Session hibernateSession; protected Class targetClass; + protected Query.Junction junction = new Query.Conjunction(); protected CriteriaQuery criteria; protected MetaClass criteriaMetaClass; protected boolean uniqueResult = false; @@ -98,6 +100,7 @@ public abstract class AbstractHibernateCriteriaBuilder extends GroovyObjectSuppo protected boolean participate; protected boolean scroll; protected boolean count; + protected Query.ProjectionList projectionList = new Query.ProjectionList(); protected List aliasStack = new ArrayList(); protected Map aliasMap = new HashMap(); protected static final String ALIAS = "_alias"; @@ -1123,66 +1126,69 @@ public Object scroll(@DelegatesTo(Criteria.class) Closure c) { @Override public Object invokeMethod(String name, Object obj) { Object[] args = obj.getClass().isArray() ? (Object[])obj : new Object[]{obj}; -// -// if (paginationEnabledList && SET_RESULT_TRANSFORMER_CALL.equals(name) && args.length == 1 && -// args[0] instanceof ResultTransformer) { -// resultTransformer = (ResultTransformer) args[0]; -// return null; -// } -// -// if (isCriteriaConstructionMethod(name, args)) { -// if (criteria != null) { -// throwRuntimeException(new IllegalArgumentException("call to [" + name + "] not supported here")); -// } -// -// if (name.equals(GET_CALL)) { -// uniqueResult = true; -// } -// else if (name.equals(SCROLL_CALL)) { -// scroll = true; -// } -// else if (name.equals(COUNT_CALL)) { -// count = true; -// } -//// else if (name.equals(LIST_DISTINCT_CALL)) { -//// resultTransformer = CriteriaSpecification.DISTINCT_ROOT_ENTITY; -//// } -// -// createCriteriaInstance(); -// -// // Check for pagination params -// if (name.equals(LIST_CALL) && args.length == 2) { -// paginationEnabledList = true; -// invokeClosureNode(args[1]); + + if (paginationEnabledList && SET_RESULT_TRANSFORMER_CALL.equals(name) && args.length == 1 && + args[0] instanceof ResultTransformer) { + resultTransformer = (ResultTransformer) args[0]; + return null; + } + + if (isCriteriaConstructionMethod(name, args)) { + if (criteria != null) { + throwRuntimeException(new IllegalArgumentException("call to [" + name + "] not supported here")); + } + + if (name.equals(GET_CALL)) { + uniqueResult = true; + } + else if (name.equals(SCROLL_CALL)) { + scroll = true; + } + else if (name.equals(COUNT_CALL)) { + count = true; + } +// else if (name.equals(LIST_DISTINCT_CALL)) { +// resultTransformer = CriteriaSpecification.DISTINCT_ROOT_ENTITY; // } -// else { -// invokeClosureNode(args[0]); + + createCriteriaInstance(); + + // Check for pagination params + if (name.equals(LIST_CALL) && args.length == 2) { + paginationEnabledList = true; + invokeClosureNode(args[1]); + } + else { + invokeClosureNode(args[0]); + } + +// if (resultTransformer != null) { +// criteria.setResultTransformer(resultTransformer); // } -// -//// if (resultTransformer != null) { -//// criteria.setResultTransformer(resultTransformer); -//// } -// Object result; -// if (!uniqueResult) { -// if (scroll) { -// -// result = hibernateSession.createQuery(criteria).scroll(); -// } -// else if (count) { -// criteria.select(cb.count(root)); -// result = hibernateSession.createQuery(criteria).getSingleResult(); -// } + Object result; + org.hibernate.query.Query query = hibernateSession.createQuery(criteria); + if (!uniqueResult) { + + if (scroll) { + + result = query.scroll(); + } + else if (count) { + criteria.select(cb.count(root)); + result = query.getSingleResult(); + } // else if (paginationEnabledList) { // // Calculate how many results there are in total. This has been // // moved to before the 'list()' invocation to avoid any "ORDER // // BY" clause added by 'populateArgumentsForCriteria()', otherwise // // an exception is thrown for non-string sort fields (GRAILS-2690). -// criteria.setFirstResult(0); -// criteria.setMaxResults(Integer.MAX_VALUE); +// query.setFirstResult(0); +// query.setMaxResults(Integer.MAX_VALUE); // // // Restore the previous projection, add settings for the pagination parameters, // // and then execute the query. -// boolean isProjection = (projectionList != null && projectionList.getLength() > 0); +// boolean isProjection = (projectionList != null && !projectionList.getProjectionList().isEmpty()); +// // criteria.setProjection(isProjection ? projectionList : null); // // for (Order orderEntry : orderEntries) { @@ -1195,7 +1201,8 @@ public Object invokeMethod(String name, Object obj) { // CriteriaSpecification.ROOT_ENTITY // ); // } -// else if (paginationEnabledList) { +// else +// if (paginationEnabledList) { // // relevant to GRAILS-5692 // criteria.setResultTransformer(resultTransformer); // } @@ -1224,35 +1231,35 @@ public Object invokeMethod(String name, Object obj) { // } // result = createPagedResultList(argMap); // } -// else { -// result = criteria.list(); -// } -// } -// else { -// result = executeUniqueResultWithProxyUnwrap(); -// } -// if (!participate) { -// closeSession(); -// } -// return result; -// } -// -// if (criteria == null) createCriteriaInstance(); -// -// MetaMethod metaMethod = getMetaClass().getMetaMethod(name, args); -// if (metaMethod != null) { -// return metaMethod.invoke(this, args); -// } -// -// metaMethod = criteriaMetaClass.getMetaMethod(name, args); -// if (metaMethod != null) { -// return metaMethod.invoke(criteria, args); -// } -// metaMethod = criteriaMetaClass.getMetaMethod(NameUtils.getSetterName(name), args); -// if (metaMethod != null) { -// return metaMethod.invoke(criteria, args); -// } -// + else { + result = query.list(); + } + } + else { + result = query.uniqueResult(); + } + if (!participate) { + closeSession(); + } + return result; + } + + if (criteria == null) createCriteriaInstance(); + + MetaMethod metaMethod = getMetaClass().getMetaMethod(name, args); + if (metaMethod != null) { + return metaMethod.invoke(this, args); + } + + metaMethod = criteriaMetaClass.getMetaMethod(name, args); + if (metaMethod != null) { + return metaMethod.invoke(criteria, args); + } + metaMethod = criteriaMetaClass.getMetaMethod(NameUtils.getSetterName(name), args); + if (metaMethod != null) { + return metaMethod.invoke(criteria, args); + } + // if (isAssociationQueryMethod(args) || isAssociationQueryWithJoinSpecificationMethod(args)) { // final boolean hasMoreThanOneArg = args.length > 1; // Object callable = hasMoreThanOneArg ? args[1] : args[0]; @@ -1334,50 +1341,51 @@ public Object invokeMethod(String name, Object obj) { // } // } // } -// else if (args.length == 1 && args[0] != null) { -// if (criteria == null) { -// throwRuntimeException(new IllegalArgumentException("call to [" + name + "] not supported here")); -// } -// -// Object value = args[0]; -// Criterion c = null; -// if (name.equals(ID_EQUALS)) { -// return eq("id", value); -// } -// -// if (name.equals(IS_NULL) || -// name.equals(IS_NOT_NULL) || -// name.equals(IS_EMPTY) || -// name.equals(IS_NOT_EMPTY)) { -// if (!(value instanceof String)) { -// throwRuntimeException(new IllegalArgumentException("call to [" + name + "] with value [" + -// value + "] requires a String value.")); -// } -// String propertyName = calculatePropertyName((String)value); -// if (name.equals(IS_NULL)) { -// c = Restrictions.isNull(propertyName); -// } -// else if (name.equals(IS_NOT_NULL)) { -// c = Restrictions.isNotNull(propertyName); -// } -// else if (name.equals(IS_EMPTY)) { -// c = Restrictions.isEmpty(propertyName); -// } -// else if (name.equals(IS_NOT_EMPTY)) { -// c = Restrictions.isNotEmpty(propertyName); -// } -// } -// -// if (c != null) { -// return addToCriteria(c); -// } -// } + else if (args.length == 1 && args[0] != null) { + if (criteria == null) { + throwRuntimeException(new IllegalArgumentException("call to [" + name + "] not supported here")); + } + + Object value = args[0]; + Query.Criterion c = null; + if (name.equals(ID_EQUALS)) { + return eq("id", value); + } + + if (name.equals(IS_NULL) || + name.equals(IS_NOT_NULL) || + name.equals(IS_EMPTY) || + name.equals(IS_NOT_EMPTY)) { + if (!(value instanceof String)) { + throwRuntimeException(new IllegalArgumentException("call to [" + name + "] with value [" + + value + "] requires a String value.")); + } + String propertyName = calculatePropertyName((String)value); + if (name.equals(IS_NULL)) { + c = Restrictions.isNull(propertyName); + } + else if (name.equals(IS_NOT_NULL)) { + c = Restrictions.isNotNull(propertyName); + } + else if (name.equals(IS_EMPTY)) { + c = Restrictions.isEmpty(propertyName); + } + else if (name.equals(IS_NOT_EMPTY)) { + c = Restrictions.isNotEmpty(propertyName); + } + } + + if (c != null) { + return addToCriteria(c); + } + } throw new MissingMethodException(name, getClass(), args); } + protected abstract List createPagedResultList(Map args); @@ -1434,7 +1442,20 @@ private void invokeClosureNode(Object args) { } - + /** + * adds and returns the given criterion to the currently active criteria set. + * this might be either the root criteria or a currently open + * LogicalExpression. + */ + protected Query.Criterion addToCriteria(Query.Criterion c) { + if (false) { +// logicalExpressionStack.get(logicalExpressionStack.size() - 1).args.add(c); + } + else { + junction.add(c); + } + return c; + } diff --git a/grails-datastore-gorm-hibernate/src/main/groovy/org/grails/orm/hibernate/query/AbstractHibernateQuery.java b/grails-datastore-gorm-hibernate/src/main/groovy/org/grails/orm/hibernate/query/AbstractHibernateQuery.java index c8b1e5d5..25e07f36 100644 --- a/grails-datastore-gorm-hibernate/src/main/groovy/org/grails/orm/hibernate/query/AbstractHibernateQuery.java +++ b/grails-datastore-gorm-hibernate/src/main/groovy/org/grails/orm/hibernate/query/AbstractHibernateQuery.java @@ -14,6 +14,7 @@ */ package org.grails.orm.hibernate.query; +import groovy.util.logging.Slf4j; import jakarta.persistence.FetchType; import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.JoinType; @@ -51,6 +52,7 @@ * @since 1.0 */ @SuppressWarnings("rawtypes") +@Slf4j public abstract class AbstractHibernateQuery extends Query { public static final String SIZE_CONSTRAINT_PREFIX = "Size"; @@ -62,7 +64,6 @@ public abstract class AbstractHibernateQuery extends Query { protected Root root; protected CriteriaQuery criteriaQuery; - protected AbstractHibernateQuery.HibernateProjectionList hibernateProjectionList; protected String alias; protected int aliasCount; protected Map createdAssociationPaths = new HashMap(); @@ -349,21 +350,6 @@ private SqmJoinType resolveJoinType(JoinType joinType) { } } - @Override - public Query max(int max) { - return this; - } - - @Override - public Query maxResults(int max) { - return this; - } - - @Override - public Query offset(int offset) { - return this; - } - @Override public Query firstResult(int offset) { offset(offset); @@ -402,9 +388,7 @@ public Query select(String property) { @Override public List list() { - org.hibernate.query.Query query = getQuery(); - System.out.println(query.getQueryString()); - return query.getResultList(); + return getQuery().getResultList(); } @@ -432,34 +416,45 @@ private static Predicate combinePredicates(Predicate... predicates) { } private org.hibernate.query.Query getQuery() { + HibernateCriteriaBuilder cb = getCriteriaBuilder(); List projections = projections() .getProjectionList() .stream() .filter(combinePredicates(projectionPredicates)).toList(); if (projections.size() == 1 && projections.get(0) instanceof CountProjection) { - criteriaQuery = getCriteriaBuilder().createQuery(Long.class); + criteriaQuery = cb.createQuery(Long.class); root = criteriaQuery.from(entity.getJavaClass()); - criteriaQuery.select(getCriteriaBuilder().count(root)); + criteriaQuery.select(cb.count(root)); } else { - criteriaQuery = getCriteriaBuilder().createQuery(entity.getJavaClass()); + criteriaQuery = cb.createQuery(entity.getJavaClass()); root = criteriaQuery.from(entity.getJavaClass()); criteriaQuery.select(root); } List predicates = this.criteria.getCriteria().stream(). map(criterion -> { - if (criterion instanceof IsNotNull isNotNull) { - return getCriteriaBuilder().isNotNull(root.get(isNotNull.getProperty())); + if (criterion instanceof IsNotNull c) { + return cb.isNotNull(root.get(c.getProperty())); + } else if (criterion instanceof Equals c ) { + return cb.equal(root.get(c.getProperty()),c.getValue()); } return null; }).filter(Objects::nonNull).toList(); - criteriaQuery.where(getCriteriaBuilder().and(predicates.toArray(new JpaPredicate[0]))); - return ((IHibernateTemplate) session.getNativeInterface()).getSessionFactory().getCurrentSession().createQuery(criteriaQuery); + criteriaQuery.where(cb.and(predicates.toArray(new JpaPredicate[0]))); + return getSessionFactory() + .getCurrentSession() + .createQuery(criteriaQuery) + .setMaxResults(this.max) + .setFirstResult(this.offset); + } + + private SessionFactory getSessionFactory() { + return ((IHibernateTemplate) session.getNativeInterface()).getSessionFactory(); } private HibernateCriteriaBuilder getCriteriaBuilder() { - return ((IHibernateTemplate) session.getNativeInterface()).getSessionFactory().getCriteriaBuilder(); + return getSessionFactory().getCriteriaBuilder(); } @@ -483,87 +478,6 @@ protected String generateAlias(String associationName) { } - - - - - protected class HibernateProjectionList extends ProjectionList { - - private boolean rowCount = false; - - public boolean isRowCount() { - return rowCount; - } - - @Override - public boolean isEmpty() { - return true; - } - - - @Override - public ProjectionList add(Projection p) { - return this; - } - - @Override - public org.grails.datastore.mapping.query.api.ProjectionList countDistinct(String property) { - return this; - } - - @Override - public org.grails.datastore.mapping.query.api.ProjectionList distinct(String property) { - return this; - } - - @Override - public org.grails.datastore.mapping.query.api.ProjectionList rowCount() { - this.rowCount = true; - return this; - } - - @Override - public ProjectionList id() { - return this; - } - - @Override - public ProjectionList count() { - this.rowCount = true; - return this; - } - - @Override - public ProjectionList property(String name) { - return this; - } - - @Override - public ProjectionList sum(String name) { - return this; - } - - @Override - public ProjectionList min(String name) { - return this; - } - - @Override - public ProjectionList max(String name) { - return this; - } - - @Override - public ProjectionList avg(String name) { - return this; - } - - @Override - public ProjectionList distinct() { - return this; - } - } - protected class HibernateAssociationQuery extends AssociationQuery { protected String alias; diff --git a/grails-datastore-gorm-hibernate/src/test/groovy/grails/gorm/tests/DetachedCriteriaProjectionSpec.groovy b/grails-datastore-gorm-hibernate/src/test/groovy/grails/gorm/tests/DetachedCriteriaProjectionSpec.groovy index f07eff4b..d8ae2b0f 100644 --- a/grails-datastore-gorm-hibernate/src/test/groovy/grails/gorm/tests/DetachedCriteriaProjectionSpec.groovy +++ b/grails-datastore-gorm-hibernate/src/test/groovy/grails/gorm/tests/DetachedCriteriaProjectionSpec.groovy @@ -2,6 +2,7 @@ package grails.gorm.tests import grails.gorm.DetachedCriteria import grails.gorm.annotation.Entity +import grails.gorm.hibernate.HibernateEntity import grails.gorm.transactions.Rollback import grails.gorm.transactions.Transactional import org.grails.datastore.mapping.query.Query @@ -24,10 +25,10 @@ class DetachedCriteriaProjectionSpec extends Specification { def setup() { DetachedEntity.findAll().each { it.delete() } Entity1.findAll().each { it.delete(flush: true) } - final entity1 = new Entity1(id: 1, field1: 'Correct').save() - new Entity1(id: 2, field1: 'Incorrect').save() - new DetachedEntity(id: 1, entityId: entity1.id, field: 'abc').save() - new DetachedEntity(id: 2, entityId: entity1.id, field: 'def').save() + final entity1 = new Entity1(id: 1, field1: 'Correct', version: 0).save() + new Entity1(id: 2, field1: 'Incorrect', version: 0).save() + new DetachedEntity(id: 1, entityId: entity1.id, field: 'abc', version: 0).save() + new DetachedEntity(id: 2, entityId: entity1.id, field: 'def', version: 0).save() } @Rollback @@ -83,18 +84,18 @@ class DetachedCriteriaProjectionSpec extends Specification { } @Entity -public class Entity1 { +public class Entity1 implements HibernateEntity { Long id String field1 static hasMany = [children : Entity2] } @Entity -class Entity2 { +class Entity2 implements HibernateEntity { static belongsTo = [parent: Entity1] String field } @Entity -class DetachedEntity { +class DetachedEntity implements HibernateEntity { Long entityId String field } \ No newline at end of file