io.micronaut.data.model.jpa.criteria.impl.AbstractPersistentEntityQuery Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of micronaut-data-model Show documentation
Show all versions of micronaut-data-model Show documentation
Data Repository Support for Micronaut
The newest version!
/*
* Copyright 2017-2021 original 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 io.micronaut.data.model.jpa.criteria.impl;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.Internal;
import io.micronaut.data.annotation.Join;
import io.micronaut.data.model.Association;
import io.micronaut.data.model.PersistentEntity;
import io.micronaut.data.model.jpa.criteria.ExpressionType;
import io.micronaut.data.model.jpa.criteria.IExpression;
import io.micronaut.data.model.jpa.criteria.IPredicate;
import io.micronaut.data.model.jpa.criteria.ISelection;
import io.micronaut.data.model.jpa.criteria.PersistentEntityCriteriaQuery;
import io.micronaut.data.model.jpa.criteria.PersistentEntityQuery;
import io.micronaut.data.model.jpa.criteria.PersistentEntityRoot;
import io.micronaut.data.model.jpa.criteria.PersistentPropertyPath;
import io.micronaut.data.model.jpa.criteria.impl.predicate.BinaryPredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.ConjunctionPredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.DisjunctionPredicate;
import io.micronaut.data.model.jpa.criteria.impl.selection.CompoundSelection;
import io.micronaut.data.model.jpa.criteria.impl.util.Joiner;
import io.micronaut.data.model.query.JoinPath;
import io.micronaut.data.model.query.builder.QueryBuilder2;
import io.micronaut.data.model.query.builder.QueryResult;
import jakarta.persistence.criteria.AbstractQuery;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.Expression;
import jakarta.persistence.criteria.From;
import jakarta.persistence.criteria.Order;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.criteria.Selection;
import jakarta.persistence.metamodel.EntityType;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import static io.micronaut.data.model.jpa.criteria.impl.CriteriaUtils.hasVersionPredicate;
import static io.micronaut.data.model.jpa.criteria.impl.CriteriaUtils.notSupportedOperation;
import static io.micronaut.data.model.jpa.criteria.impl.CriteriaUtils.requireProperty;
/**
* The abstract implementation of {@link PersistentEntityCriteriaQuery}.
*
* @param The type of the result
* @param The self type
* @author Denis Stepanov
* @since 3.2
*/
@Internal
public abstract class AbstractPersistentEntityQuery> implements AbstractQuery,
QueryResultPersistentEntityCriteriaQuery, PersistentEntityQuery {
protected Map parametersInRole = new LinkedHashMap<>();
protected final CriteriaBuilder criteriaBuilder;
protected final ExpressionType resultType;
protected Predicate predicate;
protected Selection> selection;
protected PersistentEntityRoot> entityRoot;
protected List orders;
protected int max = -1;
protected int offset = 0;
protected boolean forUpdate;
protected boolean distinct;
protected AbstractPersistentEntityQuery(ExpressionType resultType, CriteriaBuilder criteriaBuilder) {
this.resultType = resultType;
this.criteriaBuilder = criteriaBuilder;
}
public final Map getParametersInRole() {
return parametersInRole;
}
/**
* @return The self instance
*/
protected abstract Self self();
@Override
public QueryResult buildQuery(AnnotationMetadata annotationMetadata, QueryBuilder2 queryBuilder) {
return queryBuilder.buildSelect(annotationMetadata, toSelectQueryDefinition());
}
/**
* @return Build {@link io.micronaut.data.model.query.builder.QueryBuilder2.SelectQueryDefinition}.
*/
public QueryBuilder2.SelectQueryDefinition toSelectQueryDefinition() {
return new SelectQueryDefinitionImpl(
entityRoot,
entityRoot.getPersistentEntity(),
predicate,
selection == null ? entityRoot : selection,
calculateJoins(entityRoot.getPersistentEntity()),
forUpdate,
distinct,
orders == null ? List.of() : orders,
max,
offset,
parametersInRole
);
}
private Map calculateJoins(PersistentEntity persistentEntity) {
Joiner joiner = new Joiner();
if (predicate instanceof IPredicate predicateVisitable) {
predicateVisitable.visitPredicate(joiner);
}
if (selection instanceof ISelection> selectionVisitable) {
selectionVisitable.visitSelection(joiner);
entityRoot.visitSelection(joiner);
} else {
entityRoot.visitSelection(joiner);
}
if (orders != null) {
for (Order o : orders) {
joiner.joinIfNeeded(requireProperty(o.getExpression()));
}
}
Map joinPaths = new LinkedHashMap<>();
for (Map.Entry e : joiner.getJoins().entrySet()) {
Joiner.Joined joined = e.getValue();
Join.Type joinType = calculateJoinType(joined);
String path = e.getKey();
io.micronaut.data.model.PersistentPropertyPath propertyPath = persistentEntity.getPropertyPath(path);
if (propertyPath == null) {
throw new IllegalArgumentException("Invalid association path. Element [" + path + "] is not an association for [" + persistentEntity + "]");
}
Association[] associationPath;
if (propertyPath.getProperty() instanceof Association) {
associationPath = Stream.concat(
propertyPath.getAssociations().stream(),
Stream.of(propertyPath.getProperty())
).toArray(Association[]::new);
} else {
associationPath = propertyPath.getAssociations().toArray(new Association[0]);
}
JoinPath jp = new JoinPath(path, associationPath, joinType, joined.getAlias());
joinPaths.put(e.getKey(), jp);
}
return joinPaths;
}
private Join.Type calculateJoinType(Joiner.Joined joined) {
Join.Type joinType = Optional.ofNullable(joined.getType()).orElse(Join.Type.DEFAULT);
if (!isProjected(joined.getAssociation())) {
// Fetch joins don't make sense if the entity is not projected
joinType = switch (joinType) {
case INNER, FETCH -> Join.Type.DEFAULT;
case LEFT_FETCH -> Join.Type.LEFT;
case RIGHT_FETCH -> Join.Type.RIGHT;
default -> joinType;
};
}
return joinType;
}
private boolean isProjected(jakarta.persistence.criteria.Join, ?> join) {
return isProjected(selection, join);
}
private boolean isProjected(Selection> selection, jakarta.persistence.criteria.Join, ?> join) {
if (selection instanceof CompoundSelection> compoundSelection) {
// Avoid this check for now, we would need to support equals of different property paths
return true;
// for (Selection> compoundSelectionItem : compoundSelection.getCompoundSelectionItems()) {
// if (isProjected(compoundSelectionItem, join)) {
// return true;
// }
// }
// return false;
}
var root = selection == null ? entityRoot : selection;
while (join != null) {
if (join == root) {
return true;
}
From, ?> parent = join.getParent();
if (parent == root) {
return true;
}
if (parent instanceof jakarta.persistence.criteria.Join, ?> nextJoin) {
join = nextJoin;
} else {
return false;
}
}
return false;
}
@Override
public abstract PersistentEntityRoot from(Class entityClass);
@Override
public PersistentEntityRoot from(EntityType entity) {
if (entityRoot != null) {
throw new IllegalStateException("The root entity is already specified!");
}
return null;
}
public abstract PersistentEntityRoot from(PersistentEntity persistentEntity);
/**
* Sets the max rows.
*
* @param limit The max ros
* @return self
*/
public Self limit(int limit) {
this.max = limit;
return self();
}
/**
* Sets the offset.
*
* @param offset The offset
* @return self
*/
public Self offset(int offset) {
this.offset = offset;
return self();
}
/**
* Enables the query for update.
*
* @param forUpdate The for update
* @return self
*/
public Self forUpdate(boolean forUpdate) {
this.forUpdate = forUpdate;
return self();
}
@Override
public Self where(Expression restriction) {
if (restriction instanceof ConjunctionPredicate conjunctionPredicate) {
predicate = conjunctionPredicate;
} else {
predicate = new ConjunctionPredicate(Collections.singleton((IExpression) restriction));
}
return self();
}
@Override
public Self where(Predicate... restrictions) {
Objects.requireNonNull(restrictions);
if (restrictions.length > 0) {
predicate = restrictions.length == 1 ? restrictions[0] : new ConjunctionPredicate(
Arrays.stream(restrictions).sequential().map(x -> (IExpression) x).toList()
);
} else {
predicate = null;
}
return self();
}
@Override
public Self groupBy(Expression>... grouping) {
throw notSupportedOperation();
}
@Override
public Self groupBy(List> grouping) {
throw notSupportedOperation();
}
@Override
public Self having(Expression restriction) {
throw notSupportedOperation();
}
@Override
public Self having(Predicate... restrictions) {
throw notSupportedOperation();
}
@Override
public Self distinct(boolean distinct) {
this.distinct = distinct;
return self();
}
@Override
public Set> getRoots() {
if (entityRoot != null) {
return Collections.singleton(entityRoot);
}
return Collections.emptySet();
}
@Override
public List> getGroupList() {
throw notSupportedOperation();
}
@Override
public Predicate getGroupRestriction() {
throw notSupportedOperation();
}
@Override
public boolean isDistinct() {
return distinct;
}
@Override
public Class getResultType() {
return resultType.getJavaType();
}
@Override
public Selection getSelection() {
return (Selection) selection;
}
@Override
public Predicate getRestriction() {
return predicate;
}
public final boolean hasOnlyIdRestriction() {
return isOnlyIdRestriction(predicate);
}
private boolean isOnlyIdRestriction(Expression> predicate) {
if (predicate instanceof BinaryPredicate binaryPredicate) {
if (binaryPredicate.getLeftExpression() instanceof PersistentPropertyPath> pp) {
return pp.getProperty() == pp.getProperty().getOwner().getIdentity();
}
if (binaryPredicate.getRightExpression() instanceof PersistentPropertyPath> pp) {
return pp.getProperty() == pp.getProperty().getOwner().getIdentity();
}
}
if (predicate instanceof ConjunctionPredicate conjunctionPredicate) {
if (conjunctionPredicate.getPredicates().size() == 1) {
return isOnlyIdRestriction(conjunctionPredicate.getPredicates().iterator().next());
}
}
if (predicate instanceof DisjunctionPredicate disjunctionPredicate) {
if (disjunctionPredicate.getPredicates().size() == 1) {
return isOnlyIdRestriction(disjunctionPredicate.getPredicates().iterator().next());
}
}
return false;
}
public final boolean hasVersionRestriction() {
if (entityRoot.getPersistentEntity().getVersion() == null) {
return false;
}
return hasVersionPredicate(predicate);
}
@Internal
private static final class SelectQueryDefinitionImpl extends BaseQueryDefinitionImpl implements QueryBuilder2.SelectQueryDefinition {
private final Root> root;
private final Selection> selection;
private final boolean isForUpdate;
private final boolean isDistinct;
private final List order;
private final int limit;
private final int offset;
private final Map parametersInRole;
public SelectQueryDefinitionImpl(Root> root,
PersistentEntity persistentEntity,
Predicate predicate,
Selection> selection,
Map joinPaths,
boolean isForUpdate,
boolean isDistinct,
List order,
int limit,
int offset,
Map parametersInRole) {
super(persistentEntity, predicate, joinPaths);
this.root = root;
this.selection = selection;
this.isForUpdate = isForUpdate;
this.isDistinct = isDistinct;
this.order = order;
this.limit = limit;
this.offset = offset;
this.parametersInRole = parametersInRole;
}
@Override
public Map parametersInRole() {
return parametersInRole;
}
@Override
public Root> root() {
return root;
}
@Override
public Selection> selection() {
return selection;
}
@Override
public List order() {
return order;
}
@Override
public int limit() {
return limit;
}
@Override
public int offset() {
return offset;
}
@Override
public boolean isForUpdate() {
return isForUpdate;
}
@Override
public boolean isDistinct() {
return isDistinct;
}
}
@Internal
abstract static class BaseQueryDefinitionImpl implements QueryBuilder2.BaseQueryDefinition {
private final PersistentEntity persistentEntity;
private final Predicate predicate;
private final Map joinPaths;
protected BaseQueryDefinitionImpl(PersistentEntity persistentEntity,
Predicate predicate,
Map joinPaths) {
this.persistentEntity = persistentEntity;
this.predicate = predicate;
this.joinPaths = joinPaths;
}
@Override
public PersistentEntity persistentEntity() {
return persistentEntity;
}
@Override
public Predicate predicate() {
return predicate;
}
@Override
public Collection getJoinPaths() {
return Collections.unmodifiableCollection(joinPaths.values());
}
@Override
public Optional getJoinPath(String path) {
if (path != null) {
return Optional.ofNullable(joinPaths.get(path));
}
return Optional.empty();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy