com.querydsl.jpa.JPAQueryMixin Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2015, The Querydsl Team (http://www.querydsl.com/team)
*
* 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.querydsl.jpa;
import com.querydsl.core.DefaultQueryMetadata;
import com.querydsl.core.JoinFlag;
import com.querydsl.core.QueryMetadata;
import com.querydsl.core.support.Context;
import com.querydsl.core.support.PathsExtractor;
import com.querydsl.core.support.QueryMixin;
import com.querydsl.core.support.ReplaceVisitor;
import com.querydsl.core.types.CollectionExpression;
import com.querydsl.core.types.ConstantImpl;
import com.querydsl.core.types.EntityPath;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.ExpressionUtils;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.PathMetadata;
import com.querydsl.core.types.PathType;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.SubQueryExpression;
import com.querydsl.core.types.dsl.CollectionPathBase;
import jakarta.persistence.Entity;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
/**
* {@code JPAQueryMixin} extends {@link QueryMixin} to support JPQL join construction
*
* @author tiwe
* @param
*/
public class JPAQueryMixin extends QueryMixin {
private final Set> paths = new HashSet<>();
private final Map, Path>> aliases = new HashMap<>();
private final JPAMapAccessVisitor mapAccessVisitor;
private final JPAListAccessVisitor listAccessVisitor;
private final JPACollectionAnyVisitor collectionAnyVisitor;
private final ReplaceVisitor replaceVisitor =
new ReplaceVisitor<>() {
@Override
public Expression> visit(Path> expr, Void context) {
return convertPathForOrder(expr);
}
@Override
public Expression> visit(SubQueryExpression> expr, @Nullable Void context) {
// don't shorten paths inside subquery expressions
return expr;
}
};
public static final JoinFlag FETCH = new JoinFlag("fetch ");
public JPAQueryMixin() {
this(null, new DefaultQueryMetadata());
}
public JPAQueryMixin(QueryMetadata metadata) {
this(null, metadata);
}
public JPAQueryMixin(T self, QueryMetadata metadata) {
super(self, metadata);
mapAccessVisitor = new JPAMapAccessVisitor(metadata, aliases);
listAccessVisitor = new JPAListAccessVisitor(metadata, aliases);
collectionAnyVisitor = new JPACollectionAnyVisitor();
}
public T fetchJoin() {
addJoinFlag(FETCH);
return getSelf();
}
@Override
protected Expression createAlias(Expression> expr, Path alias) {
aliases.put(expr, alias);
return super.createAlias(expr, alias);
}
static boolean isEntityPath(Path> path) {
if (path instanceof CollectionPathBase) {
return isEntityPath((Path>) ((CollectionPathBase) path).any());
} else {
return path instanceof EntityPath || path.getType().isAnnotationPresent(Entity.class);
}
}
@SuppressWarnings("unchecked")
static Class getElementTypeOrType(Path path) {
if (path instanceof CollectionExpression) {
return ((CollectionExpression) path).getParameter(0);
} else {
return (Class) path.getType();
}
}
@SuppressWarnings("unchecked")
private Path shorten(Path path, List> paths) {
var metadata = path.getMetadata();
if (metadata.isRoot() || paths.contains(path)) {
return path;
} else if (aliases.containsKey(path)) {
return (Path) aliases.get(path);
} else if (metadata.getPathType() == PathType.COLLECTION_ANY) {
return (Path) shorten(metadata.getParent(), paths);
} else if (!isEntityPath(path)) {
Path> parent = shorten(metadata.getParent(), paths);
if (parent.equals(metadata.getParent())) {
return path;
} else {
return ExpressionUtils.path(
path.getType(),
new PathMetadata(parent, metadata.getElement(), metadata.getPathType()));
}
} else if (metadata.getParent().getMetadata().isRoot()) {
Class type = getElementTypeOrType(path);
Path newPath = ExpressionUtils.path(type, ExpressionUtils.createRootVariable(path));
leftJoin(path, newPath);
return newPath;
} else {
Class type = getElementTypeOrType(path);
Path> parent = shorten(metadata.getParent(), paths);
Path oldPath =
ExpressionUtils.path(
path.getType(),
new PathMetadata(parent, metadata.getElement(), metadata.getPathType()));
Path newPath = ExpressionUtils.path(type, ExpressionUtils.createRootVariable(oldPath));
aliases.put(path, newPath);
leftJoin(oldPath, newPath);
return newPath;
}
}
private Path convertPathForOrder(Path path) {
var metadata = path.getMetadata();
// at least three levels
if (metadata.getParent() != null && !metadata.getParent().getMetadata().isRoot()) {
var md = getMetadata();
var exprs = new HashSet<>(md.getGroupBy());
if (md.getProjection() != null) {
exprs.add(md.getProjection());
}
if (md.getWhere() != null) {
exprs.add(md.getWhere());
}
if (md.getHaving() != null) {
exprs.add(md.getHaving());
}
List> paths = new ArrayList<>();
// extract paths
PathsExtractor.DEFAULT.visit(exprs, paths);
if (!paths.contains(path) && !paths.contains(metadata.getParent())) {
Path> shortened = shorten(metadata.getParent(), paths);
return ExpressionUtils.path(
path.getType(),
new PathMetadata(shortened, metadata.getElement(), metadata.getPathType()));
} else {
return path;
}
} else {
return path;
}
}
@SuppressWarnings("unchecked")
@Override
public