Use SelectionQuery.getResultCount() for count queries if possible.
We now use Hibernate's built-in mechanism to obtain the result count if there is an enclosing transaction. Without the transaction, the session is being closed and we cannot run the query. Closes #3456
This commit is contained in:
@@ -30,6 +30,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
import java.util.function.LongSupplier;
|
||||
|
||||
import org.eclipse.persistence.config.QueryHints;
|
||||
import org.eclipse.persistence.jpa.JpaQuery;
|
||||
@@ -37,6 +38,7 @@ import org.eclipse.persistence.queries.ScrollableCursor;
|
||||
import org.hibernate.ScrollMode;
|
||||
import org.hibernate.ScrollableResults;
|
||||
import org.hibernate.proxy.HibernateProxy;
|
||||
import org.hibernate.query.SelectionQuery;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.data.util.CloseableIterator;
|
||||
@@ -117,6 +119,17 @@ public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, Quer
|
||||
return "org.hibernate.comment";
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getResultCount(Query resultQuery, LongSupplier countSupplier) {
|
||||
|
||||
if (TransactionSynchronizationManager.isActualTransactionActive()
|
||||
&& resultQuery instanceof SelectionQuery<?> sq) {
|
||||
return sq.getResultCount();
|
||||
}
|
||||
|
||||
return super.getResultCount(resultQuery, countSupplier);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -160,6 +173,7 @@ public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, Quer
|
||||
public String getCommentHintValue(String comment) {
|
||||
return "/* " + comment + " */";
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -197,6 +211,7 @@ public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, Quer
|
||||
public @Nullable String getCommentHintKey() {
|
||||
return null;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
private static final @Nullable Class<?> typedParameterValueClass;
|
||||
@@ -406,6 +421,18 @@ public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, Quer
|
||||
return this.present;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the result count from a {@link Query} returning the result or fall back to {@code countSupplier} if the
|
||||
* query does not provide the result count.
|
||||
*
|
||||
* @param resultQuery the query that has returned {@link Query#getResultList()}
|
||||
* @param countSupplier fallback supplier to provide the count if the query does not provide it.
|
||||
* @return the result count.
|
||||
*/
|
||||
public long getResultCount(Query resultQuery, LongSupplier countSupplier) {
|
||||
return countSupplier.getAsLong();
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds the PersistenceProvider specific interface names.
|
||||
*
|
||||
@@ -427,6 +454,7 @@ public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, Quer
|
||||
|
||||
String HIBERNATE_JPA_METAMODEL_TYPE = "org.hibernate.metamodel.model.domain.JpaMetamodel";
|
||||
String ECLIPSELINK_JPA_METAMODEL_TYPE = "org.eclipse.persistence.internal.jpa.metamodel.MetamodelImpl";
|
||||
|
||||
}
|
||||
|
||||
public CloseableIterator<Object> executeQueryWithResultStream(Query jpaQuery) {
|
||||
@@ -482,6 +510,7 @@ public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, Quer
|
||||
scrollableResults.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -531,5 +560,7 @@ public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, Quer
|
||||
scrollableCursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ public abstract class AbstractJpaQuery implements RepositoryQuery {
|
||||
} else if (method.isSliceQuery()) {
|
||||
return new SlicedExecution();
|
||||
} else if (method.isPageQuery()) {
|
||||
return new PagedExecution();
|
||||
return new PagedExecution(this.provider);
|
||||
} else if (method.isModifyingQuery()) {
|
||||
return null;
|
||||
} else {
|
||||
@@ -120,6 +120,15 @@ public abstract class AbstractJpaQuery implements RepositoryQuery {
|
||||
return method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@literal true} if the query has a dedicated count query associated with it or {@literal false} if the
|
||||
* count query shall be derived.
|
||||
*
|
||||
* @return {@literal true} if the query has a dedicated count query {@literal false} if the * count query is derived.
|
||||
* @since 3.5
|
||||
*/
|
||||
public abstract boolean hasDeclaredCountQuery();
|
||||
|
||||
/**
|
||||
* Returns the {@link EntityManager}.
|
||||
*
|
||||
|
||||
@@ -62,6 +62,7 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
|
||||
private final QuerySortRewriter querySortRewriter;
|
||||
private final Lazy<ParameterBinder> countParameterBinder;
|
||||
private final ValueEvaluationContextProvider valueExpressionContextProvider;
|
||||
private final boolean hasDeclaredCountQuery;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AbstractStringBasedJpaQuery} from the given {@link JpaQueryMethod}, {@link EntityManager} and
|
||||
@@ -101,6 +102,7 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
|
||||
this.valueExpressionContextProvider = valueExpressionDelegate.createValueContextProvider(method.getParameters());
|
||||
|
||||
this.query = TemplatedQuery.create(query, method.getEntityInformation(), queryConfiguration);
|
||||
this.hasDeclaredCountQuery = countQuery != null;
|
||||
|
||||
this.countQuery = Lazy.of(() -> {
|
||||
|
||||
@@ -130,8 +132,9 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
|
||||
"JDBC style parameters (?) are not supported for JPA queries");
|
||||
}
|
||||
|
||||
private DeclaredQuery createQuery(String queryString, boolean nativeQuery) {
|
||||
return nativeQuery ? DeclaredQuery.nativeQuery(queryString) : DeclaredQuery.jpqlQuery(queryString);
|
||||
@Override
|
||||
public boolean hasDeclaredCountQuery() {
|
||||
return hasDeclaredCountQuery;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -188,6 +188,12 @@ public abstract class JpaQueryExecution {
|
||||
*/
|
||||
static class PagedExecution extends JpaQueryExecution {
|
||||
|
||||
private final PersistenceProvider provider;
|
||||
|
||||
PagedExecution(PersistenceProvider provider) {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Object doExecute(AbstractJpaQuery repositoryQuery, JpaParametersParameterAccessor accessor) {
|
||||
@@ -195,13 +201,34 @@ public abstract class JpaQueryExecution {
|
||||
Query query = repositoryQuery.createQuery(accessor);
|
||||
|
||||
return PageableExecutionUtils.getPage(query.getResultList(), accessor.getPageable(),
|
||||
() -> count(repositoryQuery, accessor));
|
||||
() -> count(query, repositoryQuery, accessor));
|
||||
}
|
||||
|
||||
private long count(AbstractJpaQuery repositoryQuery, JpaParametersParameterAccessor accessor) {
|
||||
private long count(Query resultQuery, AbstractJpaQuery repositoryQuery, JpaParametersParameterAccessor accessor) {
|
||||
|
||||
if (repositoryQuery.hasDeclaredCountQuery()) {
|
||||
return doCount(repositoryQuery, accessor);
|
||||
}
|
||||
|
||||
return provider.getResultCount(resultQuery, () -> doCount(repositoryQuery, accessor));
|
||||
}
|
||||
|
||||
long doCount(AbstractJpaQuery repositoryQuery, JpaParametersParameterAccessor accessor) {
|
||||
|
||||
List<?> totals = repositoryQuery.createCountQuery(accessor).getResultList();
|
||||
return (totals.size() == 1 ? CONVERSION_SERVICE.convert(totals.get(0), Long.class) : totals.size());
|
||||
|
||||
if (totals.size() == 1) {
|
||||
Object result = totals.get(0);
|
||||
|
||||
if (result instanceof Number n) {
|
||||
return n.longValue();
|
||||
}
|
||||
|
||||
return CONVERSION_SERVICE.convert(result, Long.class);
|
||||
}
|
||||
|
||||
// group by count
|
||||
return totals.size();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -182,6 +182,11 @@ final class NamedQuery extends AbstractJpaQuery {
|
||||
return query;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasDeclaredCountQuery() {
|
||||
return namedCountQueryIsPresent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Query doCreateQuery(JpaParametersParameterAccessor accessor) {
|
||||
|
||||
|
||||
@@ -112,6 +112,11 @@ public class PartTreeJpaQuery extends AbstractJpaQuery {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasDeclaredCountQuery() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query doCreateQuery(JpaParametersParameterAccessor accessor) {
|
||||
return queryPreparer.createQuery(accessor);
|
||||
|
||||
@@ -81,6 +81,11 @@ class StoredProcedureJpaQuery extends AbstractJpaQuery {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasDeclaredCountQuery() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected StoredProcedureQuery createQuery(JpaParametersParameterAccessor accessor) {
|
||||
return applyHints(doCreateQuery(accessor), getQueryMethod());
|
||||
|
||||
@@ -230,6 +230,11 @@ class AbstractJpaQueryTests {
|
||||
return query;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasDeclaredCountQuery() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TypedQuery<Long> doCreateCountQuery(JpaParametersParameterAccessor accessor) {
|
||||
return countQuery;
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.mockito.quality.Strictness;
|
||||
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.provider.PersistenceProvider;
|
||||
import org.springframework.data.jpa.provider.QueryExtractor;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.query.JpaQueryExecution.ModifyingExecution;
|
||||
@@ -183,7 +184,7 @@ class JpaQueryExecutionUnitTests {
|
||||
when(jpaQuery.createQuery(Mockito.any())).thenReturn(query);
|
||||
when(countQuery.getResultList()).thenReturn(Arrays.asList(20L));
|
||||
|
||||
PagedExecution execution = new PagedExecution();
|
||||
PagedExecution execution = new PagedExecution(PersistenceProvider.GENERIC_JPA);
|
||||
execution.doExecute(jpaQuery,
|
||||
new JpaParametersParameterAccessor(parameters, new Object[] { PageRequest.of(2, 10) }));
|
||||
|
||||
@@ -199,7 +200,7 @@ class JpaQueryExecutionUnitTests {
|
||||
when(jpaQuery.createQuery(Mockito.any())).thenReturn(query);
|
||||
when(query.getResultList()).thenReturn(Arrays.asList(0L));
|
||||
|
||||
PagedExecution execution = new PagedExecution();
|
||||
PagedExecution execution = new PagedExecution(PersistenceProvider.GENERIC_JPA);
|
||||
execution.doExecute(jpaQuery,
|
||||
new JpaParametersParameterAccessor(parameters, new Object[] { PageRequest.of(0, 10) }));
|
||||
|
||||
@@ -215,7 +216,7 @@ class JpaQueryExecutionUnitTests {
|
||||
when(jpaQuery.createQuery(Mockito.any())).thenReturn(query);
|
||||
when(query.getResultList()).thenReturn(Arrays.asList(new Object(), new Object(), new Object(), new Object()));
|
||||
|
||||
PagedExecution execution = new PagedExecution();
|
||||
PagedExecution execution = new PagedExecution(PersistenceProvider.GENERIC_JPA);
|
||||
execution.doExecute(jpaQuery,
|
||||
new JpaParametersParameterAccessor(parameters, new Object[] { PageRequest.of(0, 10) }));
|
||||
|
||||
@@ -230,7 +231,7 @@ class JpaQueryExecutionUnitTests {
|
||||
when(jpaQuery.createQuery(Mockito.any())).thenReturn(query);
|
||||
when(query.getResultList()).thenReturn(Arrays.asList(new Object(), new Object(), new Object(), new Object()));
|
||||
|
||||
PagedExecution execution = new PagedExecution();
|
||||
PagedExecution execution = new PagedExecution(PersistenceProvider.GENERIC_JPA);
|
||||
execution.doExecute(jpaQuery,
|
||||
new JpaParametersParameterAccessor(parameters, new Object[] { PageRequest.of(5, 10) }));
|
||||
|
||||
@@ -247,7 +248,7 @@ class JpaQueryExecutionUnitTests {
|
||||
when(jpaQuery.createCountQuery(Mockito.any())).thenReturn(query);
|
||||
when(countQuery.getResultList()).thenReturn(Arrays.asList(20L));
|
||||
|
||||
PagedExecution execution = new PagedExecution();
|
||||
PagedExecution execution = new PagedExecution(PersistenceProvider.GENERIC_JPA);
|
||||
execution.doExecute(jpaQuery,
|
||||
new JpaParametersParameterAccessor(parameters, new Object[] { PageRequest.of(4, 4) }));
|
||||
|
||||
@@ -264,7 +265,7 @@ class JpaQueryExecutionUnitTests {
|
||||
when(jpaQuery.createCountQuery(Mockito.any())).thenReturn(query);
|
||||
when(countQuery.getResultList()).thenReturn(Arrays.asList(20L));
|
||||
|
||||
PagedExecution execution = new PagedExecution();
|
||||
PagedExecution execution = new PagedExecution(PersistenceProvider.GENERIC_JPA);
|
||||
execution.doExecute(jpaQuery,
|
||||
new JpaParametersParameterAccessor(parameters, new Object[] { PageRequest.of(4, 4) }));
|
||||
|
||||
|
||||
@@ -294,7 +294,7 @@ Sometimes, no matter how many features you try to apply, it seems impossible to
|
||||
You have the ability to get your hands on the query, right before it's sent to the `EntityManager` and "rewrite" it.
|
||||
That is, you can make any alterations at the last moment.
|
||||
Query rewriting applies to the actual query and, when applicable, to count queries.
|
||||
Count queries are optimized and therefore, either not necessary or a count is obtained through other means, such as derived from a Hibernate `SelectionQuery`.
|
||||
Count queries are optimized and therefore, either not necessary or a count is obtained through other means, such as derived from a Hibernate `SelectionQuery` if there is an enclosing transaction.
|
||||
|
||||
.Declare a QueryRewriter using `@Query`
|
||||
====
|
||||
|
||||
Reference in New Issue
Block a user