Skip to content

Commit

Permalink
Replace derived CriteriaQuery with String-based queries.
Browse files Browse the repository at this point in the history
Introduce new DSL to construct JPQL queries. Refactor ParameterMetadata to PartTreeParameterBinding.

Disable Keyset pagination with projections for Eclipselink as Eclipselink doesn't consider type hints for JPQL queries.

Closes #3588
Original pull request: #3653
  • Loading branch information
mp911de committed Jan 14, 2025
1 parent d0437dd commit e14e382
Show file tree
Hide file tree
Showing 36 changed files with 2,555 additions and 772 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -242,8 +242,8 @@ private Query applyLockMode(Query query, JpaQueryMethod method) {
return lockModeType == null ? query : query.setLockMode(lockModeType);
}

protected ParameterBinder createBinder() {
return ParameterBinderFactory.createBinder(getQueryMethod().getParameters());
ParameterBinder createBinder() {
return ParameterBinderFactory.createBinder(getQueryMethod().getParameters(), false);
}

protected Query createQuery(JpaParametersParameterAccessor parameters) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
private final DeclaredQuery query;
private final Lazy<DeclaredQuery> countQuery;
private final ValueExpressionDelegate valueExpressionDelegate;
private final QueryParameterSetter.QueryMetadataCache metadataCache = new QueryParameterSetter.QueryMetadataCache();
private final QueryRewriter queryRewriter;
private final QuerySortRewriter querySortRewriter;
private final Lazy<ParameterBinder> countParameterBinder;
Expand Down Expand Up @@ -124,11 +123,9 @@ public Query doCreateQuery(JpaParametersParameterAccessor accessor) {
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
// parameters in the query do not change.
return parameterBinder.get().bindAndPrepare(query, metadata, accessor);
return parameterBinder.get().bindAndPrepare(query, accessor);
}

String getSortedQueryString(Sort sort, ReturnedType returnedType) {
Expand All @@ -155,9 +152,8 @@ protected Query doCreateCountQuery(JpaParametersParameterAccessor accessor) {
? em.createNativeQuery(queryString) //
: em.createQuery(queryString, Long.class);

QueryParameterSetter.QueryMetadata metadata = metadataCache.getMetadata(queryString, query);

countParameterBinder.get().bind(metadata.withQuery(query), accessor, QueryParameterSetter.ErrorHandling.LENIENT);
countParameterBinder.get().bind(new QueryParameterSetter.BindableQuery(query), accessor,
QueryParameterSetter.ErrorHandling.LENIENT);

return query;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class HibernateJpaParametersParameterAccessor extends JpaParametersParameterAcce
* @param values must not be {@literal null}.
* @param em must not be {@literal null}.
*/
HibernateJpaParametersParameterAccessor(Parameters<?, ?> parameters, Object[] values, EntityManager em) {
HibernateJpaParametersParameterAccessor(JpaParameters parameters, Object[] values, EntityManager em) {

super(parameters, values);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,12 @@
*/
package org.springframework.data.jpa.repository.query;

import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Expression;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.EntityManager;

import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.support.JpqlQueryTemplates;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.data.repository.query.parser.PartTree;
import org.springframework.lang.Nullable;

/**
* Special {@link JpaQueryCreator} that creates a count projecting query.
Expand All @@ -37,39 +33,33 @@
public class JpaCountQueryCreator extends JpaQueryCreator {

private final boolean distinct;
private final ReturnedType returnedType;

/**
* Creates a new {@link JpaCountQueryCreator}.
* Creates a new {@link JpaCountQueryCreator}
*
* @param tree
* @param type
* @param builder
* @param returnedType
* @param provider
* @param templates
* @param em
*/
public JpaCountQueryCreator(PartTree tree, ReturnedType type, CriteriaBuilder builder,
ParameterMetadataProvider provider) {
public JpaCountQueryCreator(PartTree tree, ReturnedType returnedType, ParameterMetadataProvider provider,
JpqlQueryTemplates templates, EntityManager em) {

super(tree, type, builder, provider);
super(tree, returnedType, provider, templates, em);

this.distinct = tree.isDistinct();
this.returnedType = returnedType;
}

@Override
protected CriteriaQuery<? extends Object> createCriteriaQuery(CriteriaBuilder builder, ReturnedType type) {
return builder.createQuery(Long.class);
}

@Override
@SuppressWarnings("unchecked")
protected CriteriaQuery<? extends Object> complete(@Nullable Predicate predicate, Sort sort,
CriteriaQuery<? extends Object> query, CriteriaBuilder builder, Root<?> root) {

CriteriaQuery<? extends Object> select = query.select(getCountQuery(builder, root));
return predicate == null ? select : select.where(predicate);
}
protected JpqlQueryBuilder.Select buildQuery(Sort sort) {
JpqlQueryBuilder.SelectStep selectStep = JpqlQueryBuilder.selectFrom(returnedType.getDomainType());
if (this.distinct) {
selectStep = selectStep.distinct();
}

@SuppressWarnings("rawtypes")
private Expression getCountQuery(CriteriaBuilder builder, Root<?> root) {
return distinct ? builder.countDistinct(root) : builder.count(root);
return selectStep.count();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,19 @@
*/
package org.springframework.data.jpa.repository.query;

import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.EntityManager;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

import org.springframework.data.domain.KeysetScrollPosition;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpqlQueryTemplates;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.data.repository.query.parser.PartTree;
import org.springframework.lang.Nullable;
Expand All @@ -39,35 +42,67 @@ class JpaKeysetScrollQueryCreator extends JpaQueryCreator {

private final JpaEntityInformation<?, ?> entityInformation;
private final KeysetScrollPosition scrollPosition;
private final ParameterMetadataProvider provider;
private final List<ParameterBinding> syntheticBindings = new ArrayList<>();

public JpaKeysetScrollQueryCreator(PartTree tree, ReturnedType type, CriteriaBuilder builder,
ParameterMetadataProvider provider, JpaEntityInformation<?, ?> entityInformation,
KeysetScrollPosition scrollPosition) {
public JpaKeysetScrollQueryCreator(PartTree tree, ReturnedType type, ParameterMetadataProvider provider,
JpqlQueryTemplates templates, JpaEntityInformation<?, ?> entityInformation, KeysetScrollPosition scrollPosition,
EntityManager em) {

super(tree, type, builder, provider);
super(tree, type, provider, templates, em);

this.entityInformation = entityInformation;
this.scrollPosition = scrollPosition;
this.provider = provider;
}

@Override
protected CriteriaQuery<?> complete(@Nullable Predicate predicate, Sort sort, CriteriaQuery<?> query,
CriteriaBuilder builder, Root<?> root) {
public List<ParameterBinding> getBindings() {

List<ParameterBinding> partTreeBindings = super.getBindings();
List<ParameterBinding> bindings = new ArrayList<>(partTreeBindings.size() + this.syntheticBindings.size());
bindings.addAll(partTreeBindings);
bindings.addAll(this.syntheticBindings);

return bindings;
}

@Override
protected JpqlQueryBuilder.AbstractJpqlQuery createQuery(@Nullable JpqlQueryBuilder.Predicate predicate, Sort sort) {

KeysetScrollSpecification<Object> keysetSpec = new KeysetScrollSpecification<>(scrollPosition, sort,
entityInformation);
Predicate keysetPredicate = keysetSpec.createPredicate(root, builder);

CriteriaQuery<?> queryToUse = super.complete(predicate, keysetSpec.sort(), query, builder, root);
JpqlQueryBuilder.Select query = buildQuery(keysetSpec.sort());

AtomicInteger counter = new AtomicInteger(provider.getBindings().size());
JpqlQueryBuilder.Predicate keysetPredicate = keysetSpec.createJpqlPredicate(getFrom(), getEntity(), value -> {

syntheticBindings.add(provider.nextSynthetic(value, scrollPosition));
return JpqlQueryBuilder.expression(render(counter.incrementAndGet()));
});
JpqlQueryBuilder.Predicate predicateToUse = getPredicate(predicate, keysetPredicate);

if (predicateToUse != null) {
return query.where(predicateToUse);
}

return query;
}

@Nullable
private static JpqlQueryBuilder.Predicate getPredicate(@Nullable JpqlQueryBuilder.Predicate predicate,
@Nullable JpqlQueryBuilder.Predicate keysetPredicate) {

if (keysetPredicate != null) {
if (queryToUse.getRestriction() != null) {
return queryToUse.where(builder.and(queryToUse.getRestriction(), keysetPredicate));
if (predicate != null) {
return predicate.nest().and(keysetPredicate.nest());
} else {
return keysetPredicate;
}
return queryToUse.where(keysetPredicate);
}

return queryToUse;
return predicate;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,21 @@
*/
public class JpaParametersParameterAccessor extends ParametersParameterAccessor {

private final JpaParameters parameters;

/**
* Creates a new {@link ParametersParameterAccessor}.
*
* @param parameters must not be {@literal null}.
* @param values must not be {@literal null}.
*/
public JpaParametersParameterAccessor(Parameters<?, ?> parameters, Object[] values) {
public JpaParametersParameterAccessor(JpaParameters parameters, Object[] values) {
super(parameters, values);
this.parameters = parameters;
}

public JpaParameters getParameters() {
return parameters;
}

@Nullable
Expand Down
Loading

0 comments on commit e14e382

Please sign in to comment.