
com.blazebit.persistence.impl.PaginatedCriteriaBuilderImpl Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2014 Blazebit.
*
* 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.blazebit.persistence.impl;
import java.util.*;
import javax.persistence.TypedQuery;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.Metamodel;
import com.blazebit.persistence.*;
import com.blazebit.persistence.impl.builder.object.DelegatingKeysetExtractionObjectBuilder;
import com.blazebit.persistence.impl.builder.object.KeysetExtractionObjectBuilder;
import com.blazebit.persistence.impl.keyset.KeysetMode;
import com.blazebit.persistence.impl.keyset.KeysetPaginationHelper;
import com.blazebit.persistence.impl.keyset.SimpleKeysetLink;
import com.blazebit.persistence.impl.query.CTENode;
import com.blazebit.persistence.impl.query.CustomQuerySpecification;
import com.blazebit.persistence.impl.query.EntityFunctionNode;
import com.blazebit.persistence.impl.query.QuerySpecification;
import com.blazebit.persistence.spi.QueryTransformer;
/**
*
* @author Christian Beikov
* @author Moritz Becker
* @since 1.0
*/
public class PaginatedCriteriaBuilderImpl extends AbstractFullQueryBuilder, PaginatedCriteriaBuilderImpl, PaginatedCriteriaBuilderImpl, BaseFinalSetOperationBuilderImpl> implements PaginatedCriteriaBuilder {
private static final String ENTITY_PAGE_POSITION_PARAMETER_NAME = "_entityPagePositionParameter";
private static final String PAGE_POSITION_ID_QUERY_ALIAS_PREFIX = "_page_position_";
private boolean keysetExtraction;
private final KeysetPage keysetPage;
// Mutable state
private boolean needsCheck = true;
private final Object entityId;
private boolean needsNewIdList;
private KeysetMode keysetMode;
// Cache
private String cachedCountQueryString;
private String cachedExternalCountQueryString;
private String cachedIdQueryString;
private String cachedExternalIdQueryString;
public PaginatedCriteriaBuilderImpl(AbstractFullQueryBuilder, ?, ?, ?> baseBuilder, boolean keysetExtraction, KeysetPage keysetPage, Object entityId, int pageSize) {
super(baseBuilder);
if (pageSize <= 0) {
throw new IllegalArgumentException("pageSize may not be zero or negative");
}
this.keysetExtraction = keysetExtraction;
this.keysetPage = keysetPage;
this.entityId = entityId;
this.maxResults = pageSize;
updateKeysetMode();
}
public PaginatedCriteriaBuilderImpl(AbstractFullQueryBuilder, ?, ?, ?> baseBuilder, boolean keysetExtraction, KeysetPage keysetPage, int firstRow, int pageSize) {
super(baseBuilder);
if (firstRow < 0) {
throw new IllegalArgumentException("firstRow may not be negative");
}
if (pageSize <= 0) {
throw new IllegalArgumentException("pageSize may not be zero or negative");
}
this.keysetExtraction = keysetExtraction;
this.keysetPage = keysetPage;
this.firstResult = firstRow;
this.entityId = null;
this.maxResults = pageSize;
updateKeysetMode();
}
@Override
public PaginatedCriteriaBuilder setFirstResult(int firstResult) {
super.setFirstResult(firstResult);
updateKeysetMode();
return this;
}
@Override
public PaginatedCriteriaBuilder setMaxResults(int maxResults) {
super.setMaxResults(maxResults);
updateKeysetMode();
return this;
}
private void updateKeysetMode() {
KeysetMode oldMode = this.keysetMode;
this.keysetMode = KeysetPaginationHelper.getKeysetMode(keysetPage, entityId, firstResult, maxResults);
if (keysetMode == KeysetMode.NONE) {
this.keysetManager.setKeysetLink(null);
} else if (keysetMode == KeysetMode.NEXT) {
this.keysetManager.setKeysetLink(new SimpleKeysetLink(keysetPage.getHighest(), keysetMode));
} else {
this.keysetManager.setKeysetLink(new SimpleKeysetLink(keysetPage.getLowest(), keysetMode));
}
if (keysetMode != oldMode) {
clearCache();
}
}
@Override
public PaginatedCriteriaBuilder withKeysetExtraction(boolean keysetExtraction) {
this.keysetExtraction = keysetExtraction;
return this;
}
@Override
public boolean isKeysetExtraction() {
return keysetExtraction;
}
private TypedQuery getCountQuery(String countQueryString, Class resultType, boolean normalQueryMode, Set keyRestrictedLeftJoins) {
if (normalQueryMode && isEmpty(keyRestrictedLeftJoins, EnumSet.of(ClauseType.ORDER_BY, ClauseType.SELECT))) {
TypedQuery countQuery = em.createQuery(countQueryString, resultType);
parameterManager.parameterizeQuery(countQuery);
return countQuery;
}
TypedQuery baseQuery = em.createQuery(countQueryString, resultType);
Set parameterListNames = parameterManager.getParameterListNames(baseQuery);
List keyRestrictedLeftJoinAliases = getKeyRestrictedLeftJoinAliases(baseQuery, keyRestrictedLeftJoins, EnumSet.of(ClauseType.ORDER_BY, ClauseType.SELECT));
List entityFunctionNodes = getEntityFunctionNodes(baseQuery);
boolean shouldRenderCteNodes = renderCteNodes(false);
List ctes = shouldRenderCteNodes ? getCteNodes(baseQuery, false) : Collections.EMPTY_LIST;
QuerySpecification querySpecification = new CustomQuerySpecification(
this, baseQuery, parameterListNames, null, null, keyRestrictedLeftJoinAliases, entityFunctionNodes, mainQuery.cteManager.isRecursive(), ctes, shouldRenderCteNodes
);
TypedQuery countQuery = new CustomSQLTypedQuery(
querySpecification,
baseQuery,
(CommonQueryBuilder>) this,
cbf.getExtendedQuerySupport(),
parameterManager.getValuesParameters(),
parameterManager.getValuesBinders()
);
parameterManager.parameterizeQuery(countQuery);
return countQuery;
}
@Override
public PaginatedTypedQuery getQuery() {
prepareAndCheck();
// We can only use the query directly if we have no ctes, entity functions or hibernate bugs
Set keyRestrictedLeftJoins = joinManager.getKeyRestrictedLeftJoins();
boolean normalQueryMode = !isMainQuery || (!mainQuery.cteManager.hasCtes() && !joinManager.hasEntityFunctions() && keyRestrictedLeftJoins.isEmpty());
String countQueryString = getPageCountQueryStringWithoutCheck();
TypedQuery> countQuery;
if (entityId == null) {
// No reference entity id, so just do a simple count query
countQuery = getCountQuery(countQueryString, Long.class, normalQueryMode, keyRestrictedLeftJoins);
} else {
countQuery = getCountQuery(countQueryString, Object[].class, normalQueryMode, keyRestrictedLeftJoins);
}
TypedQuery> idQuery = null;
TypedQuery objectQuery;
KeysetExtractionObjectBuilder objectBuilder;
if (joinManager.hasCollections()) {
String idQueryString = getPageIdQueryStringWithoutCheck();
idQuery = getIdQuery(idQueryString, normalQueryMode, keyRestrictedLeftJoins);
objectQuery = getObjectQueryById(normalQueryMode, keyRestrictedLeftJoins);
objectBuilder = null;
} else {
Map.Entry, KeysetExtractionObjectBuilder> entry = getObjectQuery(normalQueryMode, keyRestrictedLeftJoins);
objectQuery = entry.getKey();
objectBuilder = entry.getValue();
}
PaginatedTypedQuery query = new PaginatedTypedQuery(countQuery, idQuery, objectQuery, objectBuilder, entityId, firstResult, maxResults, needsNewIdList, keysetExtraction, keysetMode, keysetPage);
return query;
}
@Override
public PagedList getResultList() {
return getQuery().getResultList();
}
@Override
public String getPageCountQueryString() {
prepareAndCheck();
return getExternalPageCountQueryString();
}
private String getPageCountQueryStringWithoutCheck() {
if (cachedCountQueryString == null) {
cachedCountQueryString = buildPageCountQueryString(false);
}
return cachedCountQueryString;
}
protected String getExternalPageCountQueryString() {
if (cachedExternalCountQueryString == null) {
cachedExternalCountQueryString = buildPageCountQueryString(true);
}
return cachedExternalCountQueryString;
}
@Override
public String getPageIdQueryString() {
prepareAndCheck();
return getExternalPageIdQueryString();
}
private String getPageIdQueryStringWithoutCheck() {
if (cachedIdQueryString == null) {
cachedIdQueryString = buildPageIdQueryString(false);
}
return cachedIdQueryString;
}
protected String getExternalPageIdQueryString() {
if (cachedExternalIdQueryString == null) {
cachedExternalIdQueryString = buildPageIdQueryString(true);
}
return cachedExternalIdQueryString;
}
@Override
public String getQueryString() {
prepareAndCheck();
return getExternalQueryString();
}
@Override
protected String getBaseQueryString() {
if (cachedQueryString == null) {
if (!joinManager.hasCollections()) {
cachedQueryString = buildObjectQueryString(false);
} else {
cachedQueryString = buildBaseQueryString(false);
}
}
return cachedQueryString;
}
protected String getExternalQueryString() {
if (cachedExternalQueryString == null) {
if (!joinManager.hasCollections()) {
cachedExternalQueryString = buildObjectQueryString(true);
} else {
cachedExternalQueryString = buildBaseQueryString(true);
}
}
return cachedExternalQueryString;
}
@Override
protected void clearCache() {
super.clearCache();
cachedCountQueryString = null;
cachedExternalCountQueryString = null;
cachedIdQueryString = null;
cachedExternalIdQueryString = null;
}
@Override
protected void prepareAndCheck() {
if (!needsCheck) {
return;
}
verifyBuilderEnded();
if (!orderByManager.hasOrderBys()) {
throw new IllegalStateException("Pagination requires at least one order by item!");
}
applyImplicitJoins();
applyExpressionTransformers();
// Paginated criteria builders always need the last order by expression to be unique
Metamodel m = em.getMetamodel();
List orderByExpressions = orderByManager.getOrderByExpressions(m);
if (!orderByExpressions.get(orderByExpressions.size() - 1).isUnique()) {
throw new IllegalStateException("The last order by item must be unique!");
}
if (keysetManager.hasKeyset()) {
keysetManager.initialize(orderByExpressions);
}
needsNewIdList = keysetExtraction || orderByManager.hasComplexOrderBys();
// No need to do the check again if no mutation occurs
needsCheck = false;
}
@SuppressWarnings("unchecked")
private Map.Entry, KeysetExtractionObjectBuilder> getObjectQuery(boolean normalQueryMode, Set keyRestrictedLeftJoins) {
String queryString = getBaseQueryString();
Class> expectedResultType;
// When the keyset is included the query obviously produces an array
if (keysetExtraction) {
expectedResultType = Object[].class;
} else {
expectedResultType = selectManager.getExpectedQueryResultType();
}
TypedQuery query;
if (normalQueryMode && isEmpty(keyRestrictedLeftJoins, EnumSet.noneOf(ClauseType.class))) {
query = (TypedQuery) em.createQuery(queryString, expectedResultType);
parameterManager.parameterizeQuery(query);
} else {
TypedQuery baseQuery = (TypedQuery) em.createQuery(queryString, expectedResultType);
Set parameterListNames = parameterManager.getParameterListNames(baseQuery);
List keyRestrictedLeftJoinAliases = getKeyRestrictedLeftJoinAliases(baseQuery, keyRestrictedLeftJoins, EnumSet.noneOf(ClauseType.class));
List entityFunctionNodes = getEntityFunctionNodes(baseQuery);
boolean shouldRenderCteNodes = renderCteNodes(false);
List ctes = shouldRenderCteNodes ? getCteNodes(baseQuery, false) : Collections.EMPTY_LIST;
QuerySpecification querySpecification = new CustomQuerySpecification(
this, baseQuery, parameterListNames, null, null, keyRestrictedLeftJoinAliases, entityFunctionNodes, mainQuery.cteManager.isRecursive(), ctes, shouldRenderCteNodes
);
query = new CustomSQLTypedQuery(
querySpecification,
baseQuery,
(CommonQueryBuilder>) this,
cbf.getExtendedQuerySupport(),
parameterManager.getValuesParameters(),
parameterManager.getValuesBinders()
);
parameterManager.parameterizeQuery(query);
}
KeysetExtractionObjectBuilder objectBuilder = null;
ObjectBuilder transformerObjectBuilder = selectManager.getSelectObjectBuilder();
if (keysetExtraction) {
int keysetSize = orderByManager.getOrderByCount();
if (transformerObjectBuilder == null) {
objectBuilder = new KeysetExtractionObjectBuilder(keysetSize);
} else {
objectBuilder = new DelegatingKeysetExtractionObjectBuilder(transformerObjectBuilder, keysetSize);
}
transformerObjectBuilder = objectBuilder;
}
if (transformerObjectBuilder != null) {
for (QueryTransformer transformer : cbf.getQueryTransformers()) {
query = transformer.transformQuery((TypedQuery) query, transformerObjectBuilder);
}
}
return new AbstractMap.SimpleEntry, KeysetExtractionObjectBuilder>(query, objectBuilder);
}
private TypedQuery
© 2015 - 2025 Weber Informatics LLC | Privacy Policy