All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.querydsl.jpa.impl.AbstractJPAQuery Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2015, The Querydsl Team (http://www.querydsl.com/team)
 *
 * Licensed 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
 * http://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 com.querydsl.jpa.impl;

import com.mysema.commons.lang.CloseableIterator;
import com.querydsl.core.DefaultQueryMetadata;
import com.querydsl.core.NonUniqueResultException;
import com.querydsl.core.QueryMetadata;
import com.querydsl.core.QueryModifiers;
import com.querydsl.core.QueryResults;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.FactoryExpression;
import com.querydsl.jpa.JPAQueryBase;
import com.querydsl.jpa.JPQLSerializer;
import com.querydsl.jpa.JPQLTemplates;
import com.querydsl.jpa.QueryHandler;
import jakarta.persistence.EntityManager;
import jakarta.persistence.FlushModeType;
import jakarta.persistence.LockModeType;
import jakarta.persistence.Query;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;

/**
 * Abstract base class for JPA API based implementations of the JPQLQuery interface
 *
 * @param  result type
 * @param  concrete subtype
 * @author tiwe
 */
public abstract class AbstractJPAQuery>
    extends JPAQueryBase {

  private static final Logger logger = Logger.getLogger(AbstractJPAQuery.class.getName());

  protected final Map> hints = new LinkedHashMap<>();

  protected final EntityManager entityManager;

  protected final QueryHandler queryHandler;

  @Nullable protected LockModeType lockMode;

  @Nullable protected FlushModeType flushMode;

  @Nullable protected FactoryExpression projection;

  public AbstractJPAQuery(EntityManager em) {
    this(em, JPAProvider.getTemplates(em), new DefaultQueryMetadata());
  }

  public AbstractJPAQuery(EntityManager em, JPQLTemplates templates, QueryMetadata metadata) {
    super(metadata, templates);
    this.queryHandler = templates.getQueryHandler();
    this.entityManager = em;
  }

  /**
   * {@inheritDoc}
   *
   * @deprecated {@code fetchCount} requires a count query to be computed. In {@code querydsl-sql},
   *     this is done by wrapping the query in a subquery, like so: {@code SELECT COUNT(*) FROM
   *     (<original query>)}. Unfortunately, JPQL - the query language of JPA - does not allow
   *     queries to project from subqueries. As a result there isn't a universal way to express
   *     count queries in JPQL. Historically QueryDSL attempts at producing a modified query to
   *     compute the number of results instead.
   *     

However, this approach only works for simple queries. Specifically queries with multiple * group by clauses and queries with a having clause turn out to be problematic. This is * because {@code COUNT(DISTINCT a, b, c)}, while valid SQL in most dialects, is not valid * JPQL. Furthermore, a having clause may refer select elements or aggregate functions and * therefore cannot be emulated by moving the predicate to the where clause instead. *

In order to support {@code fetchCount} for queries with multiple group by elements or a * having clause, we generate the count in memory instead. This means that the method simply * falls back to returning the size of {@link #fetch()}. For large result sets this may come * at a severe performance penalty. *

For very specific domain models where {@link #fetchCount()} has to be used in * conjunction with complex queries containing multiple group by elements and/or a having * clause, we recommend using the Blaze-Persistence * integration for QueryDSL. Among other advanced query features, Blaze-Persistence makes it * possible to select from subqueries in JPQL. As a result the {@code BlazeJPAQuery} provided * with the integration, implements {@code fetchCount} properly and always executes a proper * count query. */ @Override @Deprecated public long fetchCount() { try { if (getMetadata().getGroupBy().size() > 1 || getMetadata().getHaving() != null) { logger.warning( "Fetchable#fetchCount() was computed in memory! See the Javadoc for" + " AbstractJPAQuery#fetchCount for more details."); var query = createQuery(null, false); return query.getResultList().size(); } var query = createQuery(null, true); return (Long) query.getSingleResult(); } finally { reset(); } } /** * Expose the original JPA query for the given projection * * @return query */ public Query createQuery() { return createQuery(getMetadata().getModifiers(), false); } protected Query createQuery(@Nullable QueryModifiers modifiers, boolean forCount) { var serializer = serialize(forCount); var queryString = serializer.toString(); logQuery(queryString); var query = entityManager.createQuery(queryString); JPAUtil.setConstants(query, serializer.getConstants(), getMetadata().getParams()); if (modifiers != null && modifiers.isRestricting()) { Integer limit = modifiers.getLimitAsInteger(); Integer offset = modifiers.getOffsetAsInteger(); if (limit != null) { query.setMaxResults(limit); } if (offset != null) { query.setFirstResult(offset); } } if (lockMode != null) { query.setLockMode(lockMode); } if (flushMode != null) { query.setFlushMode(flushMode); } for (Map.Entry> entry : hints.entrySet()) { entry.getValue().forEach(value -> query.setHint(entry.getKey(), value)); } // set transformer, if necessary and possible Expression projection = getMetadata().getProjection(); this.projection = null; // necessary when query is reused if (!forCount && projection instanceof FactoryExpression) { if (!queryHandler.transform(query, (FactoryExpression) projection)) { this.projection = (FactoryExpression) projection; } } return query; } /** * Transforms results using FactoryExpression if ResultTransformer can't be used * * @param query query * @return results */ private List getResultList(Query query) { // TODO : use lazy fetch here? if (projection != null) { List results = query.getResultList(); List rv = new ArrayList<>(results.size()); for (Object o : results) { if (o != null) { if (!o.getClass().isArray()) { o = new Object[] {o}; } rv.add(projection.newInstance((Object[]) o)); } else { rv.add(projection.newInstance(new Object[] {null})); } } return rv; } else { return query.getResultList(); } } /** * Transforms results using FactoryExpression if ResultTransformer can't be used * * @param query query * @return single result */ @Nullable private Object getSingleResult(Query query) { if (projection != null) { var result = query.getSingleResult(); if (result != null) { if (!result.getClass().isArray()) { result = new Object[] {result}; } return projection.newInstance((Object[]) result); } else { return null; } } else { return query.getSingleResult(); } } @Override public CloseableIterator iterate() { try { var query = createQuery(); return queryHandler.iterate(query, projection); } finally { reset(); } } @Override public Stream stream() { try { var query = createQuery(); return queryHandler.stream(query, projection); } finally { reset(); } } @Override @SuppressWarnings("unchecked") public List fetch() { try { var query = createQuery(); return (List) getResultList(query); } finally { reset(); } } /** * {@inheritDoc} * * @deprecated {@code fetchResults} requires a count query to be computed. In {@code * querydsl-sql}, this is done by wrapping the query in a subquery, like so: {@code SELECT * COUNT(*) FROM (<original query>)}. Unfortunately, JPQL - the query language of JPA - * does not allow queries to project from subqueries. As a result there isn't a universal way * to express count queries in JPQL. Historically QueryDSL attempts at producing a modified * query to compute the number of results instead. *

However, this approach only works for simple queries. Specifically queries with multiple * group by clauses and queries with a having clause turn out to be problematic. This is * because {@code COUNT(DISTINCT a, b, c)}, while valid SQL in most dialects, is not valid * JPQL. Furthermore, a having clause may refer select elements or aggregate functions and * therefore cannot be emulated by moving the predicate to the where clause instead. *

In order to support {@code fetchResults} for queries with multiple group by elements or * a having clause, we generate the count in memory instead. This means that the method simply * falls back to returning the size of {@link #fetch()}. For large result sets this may come * at a severe performance penalty. *

For very specific domain models where {@link #fetchResults()} has to be used in * conjunction with complex queries containing multiple group by elements and/or a having * clause, we recommend using the Blaze-Persistence * integration for QueryDSL. Among other advanced query features, Blaze-Persistence makes it * possible to select from subqueries in JPQL. As a result the {@code BlazeJPAQuery} provided * with the integration, implements {@code fetchResults} properly and always executes a proper * count query. *

Mind that for any scenario where the count is not strictly needed separately, we * recommend to use {@link #fetch()} instead. */ @Override @Deprecated public QueryResults fetchResults() { try { var modifiers = getMetadata().getModifiers(); if (getMetadata().getGroupBy().size() > 1 || getMetadata().getHaving() != null) { logger.warning( "Fetchable#fetchResults() was computed in memory! See the Javadoc for" + " AbstractJPAQuery#fetchResults for more details."); var query = createQuery(null, false); @SuppressWarnings("unchecked") List resultList = query.getResultList(); var offset = modifiers.getOffsetAsInteger() == null ? 0 : modifiers.getOffsetAsInteger(); var limit = modifiers.getLimitAsInteger() == null ? resultList.size() : modifiers.getLimitAsInteger(); return new QueryResults<>( resultList.subList(offset, Math.min(resultList.size(), offset + limit)), modifiers, resultList.size()); } var countQuery = createQuery(null, true); long total = (Long) countQuery.getSingleResult(); if (total > 0) { var query = createQuery(modifiers, false); @SuppressWarnings("unchecked") var list = (List) getResultList(query); return new QueryResults<>(list, modifiers, total); } else { return QueryResults.emptyResults(); } } finally { reset(); } } protected void logQuery(String queryString) { if (logger.isLoggable(Level.FINEST)) { var normalizedQuery = queryString.replace('\n', ' '); logger.finest(normalizedQuery); } } @Override protected void reset() {} @Nullable @SuppressWarnings("unchecked") @Override public T fetchOne() throws NonUniqueResultException { try { var query = createQuery(getMetadata().getModifiers(), false); return (T) getSingleResult(query); } catch (jakarta.persistence.NoResultException e) { logger.log(Level.FINEST, e.getMessage(), e); return null; } catch (jakarta.persistence.NonUniqueResultException e) { throw new NonUniqueResultException(e); } finally { reset(); } } @SuppressWarnings("unchecked") public Q setLockMode(LockModeType lockMode) { this.lockMode = lockMode; return (Q) this; } @SuppressWarnings("unchecked") public Q setFlushMode(FlushModeType flushMode) { this.flushMode = flushMode; return (Q) this; } @SuppressWarnings("unchecked") public Q setHint(String name, Object value) { hints.computeIfAbsent(name, key -> new LinkedHashSet<>()); hints.get(name).add(value); return (Q) this; } @Override protected JPQLSerializer createSerializer() { return new JPQLSerializer(getTemplates(), entityManager); } protected void clone(Q query) { projection = query.projection; flushMode = query.flushMode; hints.putAll(query.hints); lockMode = query.lockMode; } /** * Clone the state of this query to a new instance with the given EntityManager * * @param entityManager entity manager * @return cloned query */ public abstract Q clone(EntityManager entityManager); /** * Clone the state of this query to a new instance with the given EntityManager and the specified * templates * * @param entityManager entity manager * @param templates templates * @return cloned query */ public abstract Q clone(EntityManager entityManager, JPQLTemplates templates); /** * Clone the state of this query to a new instance * * @return cloned query */ @Override public Q clone() { return clone(entityManager, getTemplates()); } }