Polishing.
Simplify Querydsl templates retrieve and String query caching. Update documentation. Skip selection list rewriting if the returned type is an interface. Encapsulate rewrite information for Query. Reformat code. See: #2327 Original Pull Request: #3654
This commit is contained in:
committed by
Christoph Strobl
parent
755fe2623f
commit
5d41ada346
@@ -23,8 +23,6 @@ import jakarta.persistence.Tuple;
|
||||
import jakarta.persistence.TupleElement;
|
||||
import jakarta.persistence.TypedQuery;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
@@ -34,6 +32,7 @@ import java.util.Set;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.jpa.provider.PersistenceProvider;
|
||||
import org.springframework.data.jpa.repository.EntityGraph;
|
||||
@@ -46,6 +45,8 @@ import org.springframework.data.jpa.repository.query.JpaQueryExecution.SlicedExe
|
||||
import org.springframework.data.jpa.repository.query.JpaQueryExecution.StreamExecution;
|
||||
import org.springframework.data.jpa.repository.support.QueryHints;
|
||||
import org.springframework.data.jpa.util.JpaMetamodel;
|
||||
import org.springframework.data.mapping.PreferredConstructor;
|
||||
import org.springframework.data.mapping.model.PreferredConstructorDiscoverer;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.ResultProcessor;
|
||||
import org.springframework.data.repository.query.ReturnedType;
|
||||
@@ -53,7 +54,7 @@ import org.springframework.data.util.Lazy;
|
||||
import org.springframework.jdbc.support.JdbcUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Abstract base class to implement {@link RepositoryQuery}s.
|
||||
@@ -288,8 +289,8 @@ public abstract class AbstractJpaQuery implements RepositoryQuery {
|
||||
|
||||
return returnedType.isProjecting() && returnedType.getReturnedType().isInterface()
|
||||
&& !getMetamodel().isJpaManaged(returnedType.getReturnedType()) //
|
||||
? Tuple.class //
|
||||
: null;
|
||||
? Tuple.class //
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -314,6 +315,10 @@ public abstract class AbstractJpaQuery implements RepositoryQuery {
|
||||
|
||||
private final UnaryOperator<Tuple> tupleWrapper;
|
||||
|
||||
private final boolean dtoProjection;
|
||||
|
||||
private final @Nullable PreferredConstructor<?, ?> preferredConstructor;
|
||||
|
||||
/**
|
||||
* Creates a new {@link TupleConverter} for the given {@link ReturnedType}.
|
||||
*
|
||||
@@ -336,6 +341,14 @@ public abstract class AbstractJpaQuery implements RepositoryQuery {
|
||||
|
||||
this.type = type;
|
||||
this.tupleWrapper = nativeQuery ? FallbackTupleWrapper::new : UnaryOperator.identity();
|
||||
this.dtoProjection = type.isProjecting() && !type.getReturnedType().isInterface()
|
||||
&& !type.getInputProperties().isEmpty();
|
||||
|
||||
if (this.dtoProjection) {
|
||||
this.preferredConstructor = PreferredConstructorDiscoverer.discover(String.class);
|
||||
} else {
|
||||
this.preferredConstructor = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -356,23 +369,26 @@ public abstract class AbstractJpaQuery implements RepositoryQuery {
|
||||
}
|
||||
}
|
||||
|
||||
if(type.isProjecting() && !type.getReturnedType().isInterface() && !type.getInputProperties().isEmpty()) {
|
||||
List<Object> ctorArgs = new ArrayList<>(type.getInputProperties().size());
|
||||
type.getInputProperties().forEach(it -> {
|
||||
ctorArgs.add(tuple.get(it));
|
||||
});
|
||||
try {
|
||||
return type.getReturnedType().getConstructor(ctorArgs.stream().map(Object::getClass).toArray(Class<?>[]::new)).newInstance(ctorArgs.toArray());
|
||||
} catch (InstantiationException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
if (dtoProjection) {
|
||||
|
||||
Object[] ctorArgs = new Object[type.getInputProperties().size()];
|
||||
|
||||
for (int i = 0; i < type.getInputProperties().size(); i++) {
|
||||
ctorArgs[i] = tuple.get(i);
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
if (preferredConstructor.getParameterCount() == ctorArgs.length) {
|
||||
return BeanUtils.instantiateClass(preferredConstructor.getConstructor(), ctorArgs);
|
||||
}
|
||||
|
||||
return BeanUtils.instantiateClass(type.getReturnedType()
|
||||
.getConstructor(Arrays.stream(ctorArgs).map(Object::getClass).toArray(Class<?>[]::new)), ctorArgs);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
ReflectionUtils.handleReflectionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
return new TupleBackedMap(tupleWrapper.apply(tuple));
|
||||
}
|
||||
|
||||
@@ -69,8 +69,7 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
|
||||
* @param valueExpressionDelegate must not be {@literal null}.
|
||||
*/
|
||||
public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, String queryString,
|
||||
@Nullable String countQueryString, QueryRewriter queryRewriter,
|
||||
ValueExpressionDelegate valueExpressionDelegate) {
|
||||
@Nullable String countQueryString, QueryRewriter queryRewriter, ValueExpressionDelegate valueExpressionDelegate) {
|
||||
|
||||
super(method, em);
|
||||
|
||||
@@ -99,15 +98,17 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
|
||||
});
|
||||
|
||||
this.queryRewriter = queryRewriter;
|
||||
ReturnedType returnedType = method.getResultProcessor().getReturnedType();
|
||||
|
||||
JpaParameters parameters = method.getParameters();
|
||||
if ((parameters.hasPageableParameter() || parameters.hasSortParameter()) && !parameters.hasDynamicProjection()) {
|
||||
this.querySortRewriter = new CachingQuerySortRewriter();
|
||||
} else if (returnedType.isProjecting() && !returnedType.getReturnedType().isInterface()) {
|
||||
this.querySortRewriter = new ProjectingSortRewriter();
|
||||
|
||||
if (parameters.hasDynamicProjection()) {
|
||||
this.querySortRewriter = SimpleQuerySortRewriter.INSTANCE;
|
||||
} else {
|
||||
this.querySortRewriter = NoOpQuerySortRewriter.INSTANCE;
|
||||
if (parameters.hasPageableParameter() || parameters.hasSortParameter()) {
|
||||
this.querySortRewriter = new CachingQuerySortRewriter();
|
||||
} else {
|
||||
this.querySortRewriter = new UnsortedCachingQuerySortRewriter();
|
||||
}
|
||||
}
|
||||
|
||||
Assert.isTrue(method.isNativeQuery() || !query.usesJdbcStyleParameters(),
|
||||
@@ -119,19 +120,13 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
|
||||
|
||||
Sort sort = accessor.getSort();
|
||||
ResultProcessor processor = getQueryMethod().getResultProcessor().withDynamicProjection(accessor);
|
||||
|
||||
String sortedQueryString = null;
|
||||
if(querySortRewriter.equals(NoOpQuerySortRewriter.INSTANCE) && accessor.findDynamicProjection() != null && !accessor.findDynamicProjection().isInterface()) {
|
||||
sortedQueryString = getSortedQueryString(new ProjectingSortRewriter(), query, sort, processor.getReturnedType());
|
||||
} else {
|
||||
sortedQueryString = getSortedQueryString(sort, processor.getReturnedType());
|
||||
}
|
||||
|
||||
Query query = createJpaQuery(sortedQueryString, sort, accessor.getPageable(), processor.getReturnedType());
|
||||
ReturnedType returnedType = processor.getReturnedType();
|
||||
String sortedQueryString = getSortedQueryString(sort, returnedType);
|
||||
Query query = createJpaQuery(sortedQueryString, sort, accessor.getPageable(), returnedType);
|
||||
|
||||
QueryParameterSetter.QueryMetadata metadata = metadataCache.getMetadata(sortedQueryString, query);
|
||||
|
||||
// it is ok to reuse the binding contained in the ParameterBinder although we create a new query String because the
|
||||
// it is ok to reuse the binding contained in the ParameterBinder, although we create a new query String because the
|
||||
// parameters in the query do not change.
|
||||
return parameterBinder.get().bindAndPrepare(query, metadata, accessor);
|
||||
}
|
||||
@@ -140,10 +135,6 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
|
||||
return querySortRewriter.getSorted(query, sort, returnedType);
|
||||
}
|
||||
|
||||
private static String getSortedQueryString(QuerySortRewriter rewriter, DeclaredQuery query, Sort sort, ReturnedType returnedType) {
|
||||
return rewriter.getSorted(query, sort, returnedType);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ParameterBinder createBinder() {
|
||||
return createBinder(query);
|
||||
@@ -223,8 +214,8 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
|
||||
|
||||
String applySorting(CachableQuery cachableQuery) {
|
||||
|
||||
return QueryEnhancerFactory.forQuery(cachableQuery.getDeclaredQuery()).rewrite(cachableQuery.getSort(),
|
||||
cachableQuery.getReturnedType());
|
||||
return QueryEnhancerFactory.forQuery(cachableQuery.getDeclaredQuery())
|
||||
.rewrite(new DefaultQueryRewriteInformation(cachableQuery.getSort(), cachableQuery.getReturnedType()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -237,21 +228,17 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
|
||||
/**
|
||||
* No-op query rewriter.
|
||||
*/
|
||||
enum NoOpQuerySortRewriter implements QuerySortRewriter {
|
||||
enum SimpleQuerySortRewriter implements QuerySortRewriter {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
public String getSorted(DeclaredQuery query, Sort sort, ReturnedType returnedType) {
|
||||
|
||||
if (sort.isSorted()) {
|
||||
throw new UnsupportedOperationException("NoOpQueryCache does not support sorting");
|
||||
}
|
||||
|
||||
return query.getQueryString();
|
||||
return QueryEnhancerFactory.forQuery(query).rewrite(new DefaultQueryRewriteInformation(sort, returnedType));
|
||||
}
|
||||
}
|
||||
|
||||
static class ProjectingSortRewriter implements QuerySortRewriter {
|
||||
static class UnsortedCachingQuerySortRewriter implements QuerySortRewriter {
|
||||
|
||||
private volatile String cachedQueryString;
|
||||
|
||||
@@ -263,7 +250,8 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
|
||||
|
||||
String cachedQueryString = this.cachedQueryString;
|
||||
if (cachedQueryString == null) {
|
||||
this.cachedQueryString = cachedQueryString = QueryEnhancerFactory.forQuery(query).rewrite(sort, returnedType);
|
||||
this.cachedQueryString = cachedQueryString = QueryEnhancerFactory.forQuery(query)
|
||||
.rewrite(new DefaultQueryRewriteInformation(sort, returnedType));
|
||||
}
|
||||
|
||||
return cachedQueryString;
|
||||
|
||||
@@ -18,7 +18,6 @@ package org.springframework.data.jpa.repository.query;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.repository.query.ReturnedType;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@@ -54,8 +53,8 @@ public class DefaultQueryEnhancer implements QueryEnhancer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String rewrite(Sort sort, ReturnedType returnedType) {
|
||||
return QueryUtils.applySorting(this.query.getQueryString(), sort, alias);
|
||||
public String rewrite(QueryRewriteInformation rewriteInformation) {
|
||||
return QueryUtils.applySorting(this.query.getQueryString(), rewriteInformation.getSort(), alias);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2024 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* 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 org.springframework.data.jpa.repository.query;
|
||||
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.repository.query.ReturnedType;
|
||||
|
||||
/**
|
||||
* Default {@link org.springframework.data.jpa.repository.query.QueryEnhancer.QueryRewriteInformation} implementation.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
record DefaultQueryRewriteInformation(Sort sort,
|
||||
ReturnedType returnedType) implements QueryEnhancer.QueryRewriteInformation {
|
||||
@Override
|
||||
public Sort getSort() {
|
||||
return sort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReturnedType getReturnedType() {
|
||||
return returnedType();
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import org.springframework.data.repository.query.ReturnedType;
|
||||
* </ul>
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 3.5
|
||||
*/
|
||||
class DtoProjectionTransformerDelegate {
|
||||
|
||||
@@ -40,7 +41,8 @@ class DtoProjectionTransformerDelegate {
|
||||
|
||||
public QueryTokenStream transformSelectionList(QueryTokenStream selectionList) {
|
||||
|
||||
if (!returnedType.isProjecting() || selectionList.stream().anyMatch(it -> it.equals(TOKEN_NEW))) {
|
||||
if (!returnedType.isProjecting() || returnedType.getReturnedType().isInterface()
|
||||
|| selectionList.stream().anyMatch(it -> it.equals(TOKEN_NEW))) {
|
||||
return selectionList;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,6 @@ import java.util.Set;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.repository.query.ReturnedType;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
@@ -298,17 +297,20 @@ public class JSqlParserQueryEnhancer implements QueryEnhancer {
|
||||
|
||||
@Override
|
||||
public String applySorting(Sort sort) {
|
||||
return applySorting(sort, detectAlias());
|
||||
return doApplySorting(sort, detectAlias());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String rewrite(Sort sort, ReturnedType returnedType) {
|
||||
return applySorting(sort, primaryAlias);
|
||||
public String rewrite(QueryRewriteInformation rewriteInformation) {
|
||||
return doApplySorting(rewriteInformation.getSort(), primaryAlias);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String applySorting(Sort sort, @Nullable String alias) {
|
||||
return doApplySorting(sort, alias);
|
||||
}
|
||||
|
||||
private String doApplySorting(Sort sort, @Nullable String alias) {
|
||||
String queryString = query.getQueryString();
|
||||
Assert.hasText(queryString, "Query must not be null or empty");
|
||||
|
||||
|
||||
@@ -200,8 +200,9 @@ class JpaQueryEnhancer implements QueryEnhancer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String rewrite(Sort sort, ReturnedType returnedType) {
|
||||
return QueryRenderer.TokenRenderer.render(sortFunction.apply(sort, detectAlias(), returnedType).visit(context));
|
||||
public String rewrite(QueryRewriteInformation rewriteInformation) {
|
||||
return QueryRenderer.TokenRenderer.render(sortFunction
|
||||
.apply(rewriteInformation.getSort(), detectAlias(), rewriteInformation.getReturnedType()).visit(context));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -319,6 +320,13 @@ class JpaQueryEnhancer implements QueryEnhancer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Functional interface to rewrite a query considering {@link Sort} and {@link ReturnedType}. The function returns a
|
||||
* visitor object that can visit the parsed query tree.
|
||||
*
|
||||
* @since 3.5
|
||||
*/
|
||||
@FunctionalInterface
|
||||
interface SortedQueryRewriteFunction {
|
||||
|
||||
ParseTreeVisitor<? extends Object> apply(Sort sort, String primaryAlias, @Nullable ReturnedType returnedType);
|
||||
|
||||
@@ -82,11 +82,19 @@ public interface QueryEnhancer {
|
||||
* @param sort the sort specification to apply.
|
||||
* @param alias the alias to be used in the order by clause. May be {@literal null} or empty.
|
||||
* @return the modified query string.
|
||||
* @deprecated since 3.5, use {@link #rewrite(Sort, ReturnedType)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Deprecated(since = "3.5", forRemoval = true)
|
||||
String applySorting(Sort sort, @Nullable String alias);
|
||||
|
||||
String rewrite(Sort sort, ReturnedType returnedType);
|
||||
/**
|
||||
* Rewrite the query to include sorting and apply {@link ReturnedType} customizations.
|
||||
*
|
||||
* @param rewriteInformation the rewrite information to apply.
|
||||
* @return the modified query string.
|
||||
* @since 3.5
|
||||
*/
|
||||
String rewrite(QueryRewriteInformation rewriteInformation);
|
||||
|
||||
/**
|
||||
* Creates a count projected query from the given original query.
|
||||
@@ -105,4 +113,22 @@ public interface QueryEnhancer {
|
||||
*/
|
||||
String createCountQueryFor(@Nullable String countProjection);
|
||||
|
||||
/**
|
||||
* Interface to describe the information needed to rewrite a query.
|
||||
*
|
||||
* @since 3.5
|
||||
*/
|
||||
interface QueryRewriteInformation {
|
||||
|
||||
/**
|
||||
* @return the sort specification to apply.
|
||||
*/
|
||||
Sort getSort();
|
||||
|
||||
/**
|
||||
* @return type expected to be returned by the query.
|
||||
*/
|
||||
ReturnedType getReturnedType();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -83,17 +83,16 @@ class FetchableFluentQueryByPredicate<S, R> extends FluentQuerySupport<S, R> imp
|
||||
BiFunction<Sort, Pageable, AbstractJPAQuery<?, ?>> pagedFinder, Function<Predicate, Long> countOperation,
|
||||
Function<Predicate, Boolean> existsOperation, EntityManager entityManager, ProjectionFactory projectionFactory) {
|
||||
this(entityPath, predicate, entityInformation, (Class<R>) entityInformation.getJavaType(), Sort.unsorted(), 0,
|
||||
Collections.emptySet(), finder, scrollQueryFactory,
|
||||
pagedFinder, countOperation, existsOperation, entityManager, projectionFactory);
|
||||
Collections.emptySet(), finder, scrollQueryFactory, pagedFinder, countOperation, existsOperation, entityManager,
|
||||
projectionFactory);
|
||||
}
|
||||
|
||||
private FetchableFluentQueryByPredicate(EntityPath<?> entityPath, Predicate predicate,
|
||||
JpaEntityInformation<S, ?> entityInformation, Class<R> resultType, Sort sort, int limit,
|
||||
Collection<String> properties, Function<Sort, AbstractJPAQuery<?, ?>> finder,
|
||||
ScrollQueryFactory<AbstractJPAQuery<?, ?>> scrollQueryFactory,
|
||||
BiFunction<Sort, Pageable, AbstractJPAQuery<?, ?>> pagedFinder,
|
||||
Function<Predicate, Long> countOperation, Function<Predicate, Boolean> existsOperation,
|
||||
EntityManager entityManager, ProjectionFactory projectionFactory) {
|
||||
BiFunction<Sort, Pageable, AbstractJPAQuery<?, ?>> pagedFinder, Function<Predicate, Long> countOperation,
|
||||
Function<Predicate, Boolean> existsOperation, EntityManager entityManager, ProjectionFactory projectionFactory) {
|
||||
|
||||
super(resultType, sort, limit, properties, entityInformation.getJavaType(), projectionFactory);
|
||||
this.entityInformation = entityInformation;
|
||||
@@ -142,8 +141,7 @@ class FetchableFluentQueryByPredicate<S, R> extends FluentQuerySupport<S, R> imp
|
||||
|
||||
return new FetchableFluentQueryByPredicate<>(entityPath, predicate, entityInformation, resultType, sort, limit,
|
||||
mergeProperties(properties), finder, scrollQueryFactory, pagedFinder, countOperation, existsOperation,
|
||||
entityManager,
|
||||
projectionFactory);
|
||||
entityManager, projectionFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -296,6 +294,9 @@ class FetchableFluentQueryByPredicate<S, R> extends FluentQuerySupport<S, R> imp
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 3.5
|
||||
*/
|
||||
private static class DtoProjection extends ExpressionBase<Object> {
|
||||
|
||||
private final Expression<?>[] projection;
|
||||
|
||||
@@ -20,6 +20,8 @@ import jakarta.persistence.Tuple;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import com.querydsl.core.types.Expression;
|
||||
import com.querydsl.core.types.ExpressionBase;
|
||||
import com.querydsl.core.types.ExpressionUtils;
|
||||
@@ -30,6 +32,15 @@ import com.querydsl.core.types.Projections;
|
||||
import com.querydsl.core.types.Visitor;
|
||||
import com.querydsl.jpa.JPQLSerializer;
|
||||
|
||||
/**
|
||||
* Expression based on a {@link Tuple}. It's a simplified variant of {@link com.querydsl.core.types.QTuple} without
|
||||
* being a {@link com.querydsl.core.types.FactoryExpressionBase} as we do not want Querydsl to instantiate any tuples.
|
||||
* JPA is doing that for us.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 3.5
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
class JakartaTuple extends ExpressionBase<Tuple> {
|
||||
|
||||
private final List<Expression<?>> args;
|
||||
@@ -61,7 +72,8 @@ class JakartaTuple extends ExpressionBase<Tuple> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R, C> R accept(Visitor<R, C> v, C context) {
|
||||
@Nullable
|
||||
public <R, C> R accept(Visitor<R, C> v, @Nullable C context) {
|
||||
|
||||
if (v instanceof JPQLSerializer) {
|
||||
return Projections.tuple(args).accept(v, context);
|
||||
@@ -74,8 +86,7 @@ class JakartaTuple extends ExpressionBase<Tuple> {
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
} else if (obj instanceof FactoryExpression) {
|
||||
FactoryExpression<?> c = (FactoryExpression<?>) obj;
|
||||
} else if (obj instanceof FactoryExpression<?> c) {
|
||||
return args.equals(c.getArgs()) && getType().equals(c.getType());
|
||||
} else {
|
||||
return false;
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.springframework.data.domain.Sort.Order;
|
||||
import org.springframework.data.jpa.provider.PersistenceProvider;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.querydsl.QSort;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.querydsl.core.types.EntityPath;
|
||||
@@ -37,6 +38,7 @@ import com.querydsl.core.types.dsl.PathBuilder;
|
||||
import com.querydsl.jpa.EclipseLinkTemplates;
|
||||
import com.querydsl.jpa.HQLTemplates;
|
||||
import com.querydsl.jpa.JPQLQuery;
|
||||
import com.querydsl.jpa.JPQLTemplates;
|
||||
import com.querydsl.jpa.impl.AbstractJPAQuery;
|
||||
import com.querydsl.jpa.impl.JPAQuery;
|
||||
|
||||
@@ -77,11 +79,25 @@ public class Querydsl {
|
||||
*/
|
||||
public <T> AbstractJPAQuery<T, JPAQuery<T>> createQuery() {
|
||||
|
||||
return switch (provider) {
|
||||
case ECLIPSELINK -> new SpringDataJpaQuery<>(em, EclipseLinkTemplates.DEFAULT);
|
||||
case HIBERNATE -> new SpringDataJpaQuery<>(em, HQLTemplates.DEFAULT);
|
||||
default -> new SpringDataJpaQuery<>(em);
|
||||
};
|
||||
JPQLTemplates templates = getTemplates();
|
||||
return templates != null ? new SpringDataJpaQuery<>(em, templates) : new SpringDataJpaQuery<>(em);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the {@link JPQLTemplates} for the configured {@link EntityManager}. Can return {@literal null} to use the
|
||||
* default templates.
|
||||
*
|
||||
* @return the {@link JPQLTemplates} for the configured {@link EntityManager} or {@literal null} to use the default.
|
||||
* @since 3.5
|
||||
*/
|
||||
@Nullable
|
||||
public JPQLTemplates getTemplates() {
|
||||
|
||||
return switch (provider) {
|
||||
case ECLIPSELINK -> EclipseLinkTemplates.DEFAULT;
|
||||
case HIBERNATE -> HQLTemplates.DEFAULT;
|
||||
default -> JPQLTemplates.DEFAULT;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -198,11 +214,11 @@ public class Querydsl {
|
||||
|
||||
Assert.notNull(nullHandling, "NullHandling must not be null");
|
||||
|
||||
return switch (nullHandling) {
|
||||
case NULLS_FIRST -> NullHandling.NullsFirst;
|
||||
case NULLS_LAST -> NullHandling.NullsLast;
|
||||
default -> NullHandling.Default;
|
||||
};
|
||||
return switch (nullHandling) {
|
||||
case NULLS_FIRST -> NullHandling.NullsFirst;
|
||||
case NULLS_LAST -> NullHandling.NullsLast;
|
||||
default -> NullHandling.Default;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -32,7 +32,11 @@ import com.querydsl.jpa.impl.JPAQuery;
|
||||
import com.querydsl.jpa.impl.JPAUtil;
|
||||
|
||||
/**
|
||||
* Customized String-Query implementation that specifically routes tuple query creation to
|
||||
* {@code EntityManager#createQuery(queryString, Tuple.class)}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 3.5
|
||||
*/
|
||||
class SpringDataJpaQuery<T> extends JPAQuery<T> {
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ This query gets rewritten to `SELECT new UserDto(u.firstname, u.lastname) FROM U
|
||||
[WARNING]
|
||||
====
|
||||
JPQL constructor expressions must not contain aliases for selected columns.
|
||||
While `SELECT u as user, count(u.roles) as roleCount FROM USER u ...` is a valid usecase for interface based projections that rely on column names from the returned `Tuple`, the same construct is invalid when requesting a DTO where it needs to be `SELECT u, count(u.roles) FROM USER u ...`. +
|
||||
While `SELECT u as user, count(u.roles) as roleCount FROM USER u …` is a valid query for interface-based projections that rely on column names from the returned `Tuple`, the same construct is invalid when requesting a DTO where it needs to be `SELECT u, count(u.roles) FROM USER u …`. +
|
||||
Some persistence providers may be lenient about this, others not.
|
||||
====
|
||||
|
||||
|
||||
Reference in New Issue
Block a user