В современных приложениях корпоративного уровня динамический запрос является очень распространенным требованием. Весна Data JPA Предоставляет мощный набор инструментов, включая Specification
、CriteriaBuilder
и Predicate
,Может помочь нам построить сложные динамические запросы. В этой статье будет подробно описано использование этих инструментов.,И продемонстрировано на практическом примере, таком как Spring Data JPA. Реализуйте стимулирующий запрос в .
Прежде чем мы начнем писать код, нам необходимо понять несколько ключевых концепций и классов:
Specification
да Spring Data JPA Предоставляет интерфейс для создания JPA Criteria Запрос. обычно это идет с CriteriaBuilder
и Predicate
используются вместе.public interface Specification<T> { Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder); }
Specification
интерфейссерединаиз toPredicate
Метод используется для преобразования условий запроса в JPA из Predicate
объект.CriteriaBuilder
да JPA Предоставляет интерфейс для создания Запросизразличные части,Такие как условия(Predicate
)、сортировать(Order
)ждать。equal(Expression<?> x, Object y)
:строитьждать于条件like(Expression<String> x, String pattern)
:строить模糊Запрос条件greaterThan(Expression<? extends Comparable> x, Comparable y)
:Построить лучше, чем состояниеlessThan(Expression<? extends Comparable> x, Comparable y)
:Построить меньше, чем условиеand(Predicate... restrictions)
:строить AND Комбинированные условияor(Predicate... restrictions)
:строить OR Комбинированные условияPredicate
да JPA Criteria Условное выражение в запросе используется для создания сложных условий запроса.public interface Predicate extends Expression<Boolean> { }
Чтобы лучше понять эти концепции, мы продемонстрируем, как использовать эти инструменты для динамических запросов на примере простой системы запросов к книгам.
Book
Сначала мы определили простой из Book
Класс сущностей, который содержит такие поля, как название книги, автор и дата публикации.
import javax.persistence.*;
import java.util.Date;
@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String author;
private Date publishDate;
// Getters and setters omitted for brevity
}
BookQueryCriteria
Далее мы определяем BookQueryCriteria
Класс, используемый для инкапсуляции условий запроса пользователя. Эти условия будут использоваться в динамических запросах.
import cn.jichuang.annotation.Query;
import lombok.Data;
import java.util.Date;
@Data
public class BookQueryCriteria {
@Query(type = Query.Type.INNER_LIKE)
private String title;
@Query(type = Query.Type.INNER_LIKE)
private String author;
@Query(type = Query.Type.GREATER_THAN, propName = "publishDate")
private Date publishDateFrom;
@Query(type = Query.Type.LESS_THAN, propName = "publishDate")
private Date publishDateTo;
}
BookRepository
интерфейсМы определили BookRepository
интерфейс, который наследуется от JpaRepository
и JpaSpecificationExecutor
。JpaSpecificationExecutor
интерфейс обеспечивает динамический запрос возможностей.
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface BookRepository extends JpaRepository<Book, Long>, JpaSpecificationExecutor<Book> {
}
QueryHelp
Мы реализуем QueryHelp
Класс инструмента для динамического построения на основе условий запроса Predicate
объект.
package cn.jichuang.utils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import cn.jichuang.annotation.DataPermission;
import cn.jichuang.annotation.Query;
import javax.persistence.criteria.*;
import java.lang.reflect.Field;
import java.util.*;
@Slf4j
@SuppressWarnings({"unchecked","all"})
public class QueryHelp {
public static <R, Q> Predicate getPredicate(Root<R> root, Q query, CriteriaBuilder cb) {
List<Predicate> list = new ArrayList<>();
if(query == null){
return cb.and(list.toArray(new Predicate[0]));
}
try {
List<Field> fields = getAllFields(query.getClass(), new ArrayList<>());
for (Field field : fields) {
boolean accessible = field.isAccessible();
field.setAccessible(true);
Query q = field.getAnnotation(Query.class);
if (q != null) {
String propName = q.propName();
String joinName = q.joinName();
String blurry = q.blurry();
String attributeName = isBlank(propName) ? field.getName() : propName;
Class<?> fieldType = field.getType();
Object val = field.get(query);
if (ObjectUtil.isNull(val) || "".equals(val)) {
continue;
}
Join join = null;
if (ObjectUtil.isNotEmpty(blurry)) {
String[] blurrys = blurry.split(",");
List<Predicate> orPredicate = new ArrayList<>();
for (String s : blurrys) {
orPredicate.add(cb.like(root.get(s).as(String.class), "%" + val.toString() + "%"));
}
Predicate[] p = new Predicate[orPredicate.size()];
list.add(cb.or(orPredicate.toArray(p)));
continue;
}
if (ObjectUtil.isNotEmpty(joinName)) {
String[] joinNames = joinName.split(">");
for (String name : joinNames) {
switch (q.join()) {
case LEFT:
if(ObjectUtil.isNotNull(join) && ObjectUtil.isNotNull(val)){
join = join.join(name, JoinType.LEFT);
} else {
join = root.join(name, JoinType.LEFT);
}
break;
case RIGHT:
if(ObjectUtil.isNotNull(join) && ObjectUtil.isNotNull(val)){
join = join.join(name, JoinType.RIGHT);
} else {
join = root.join(name, JoinType.RIGHT);
}
break;
case INNER:
if(ObjectUtil.isNotNull(join) && ObjectUtil.isNotNull(val)){
join = join.join(name, JoinType.INNER);
} else {
join = root.join(name, JoinType.INNER);
}
break;
default: break;
}
}
}
switch (q.type()) {
case EQUAL:
list.add(cb.equal(getExpression(attributeName,join,root).as((Class<? extends Comparable>) fieldType),val));
break;
case GREATER_THAN:
list.add(cb.greaterThanOrEqualTo(getExpression(attributeName,join,root).as((Class<? extends Comparable>) fieldType), (Comparable) val));
break;
case LESS_THAN:
list.add(cb.lessThanOrEqualTo(getExpression(attributeName,join,root).as((Class<? extends Comparable>) fieldType), (Comparable) val));
break;
case LESS_THAN_NQ:
list.add(cb.lessThan(getExpression(attributeName,join,root).as((Class<? extends Comparable>) fieldType), (Comparable) val));
break;
case INNER_LIKE:
list.add(cb.like(getExpression(attributeName,join,root).as(String.class), "%" + val.toString() + "%"));
break;
case LEFT_LIKE:
list.add(cb.like(getExpression(attributeName,join,root).as(String.class), "%" + val.toString()));
break;
case RIGHT_LIKE:
list.add(cb.like(getExpression(attributeName,join,root).as(String.class), val.toString() + "%"));
break;
case IN:
if (CollUtil.isNotEmpty((Collection<Object>)val)) {
list.add(getExpression(attributeName,join,root).in((Collection<Object>) val));
}
break;
case NOT_IN:
if (CollUtil.isNotEmpty((Collection<Object>)val)) {
list.add(getExpression(attributeName,join,root).in((Collection<Object>) val).not());
}
break;
case NOT_EQUAL:
list.add(cb.notEqual(getExpression(attributeName,join,root), val));
break;
case NOT_NULL:
list.add(cb.isNotNull(getExpression(attributeName,join,root)));
break;
case IS_NULL:
list.add(cb.isNull(getExpression(attributeName,join,root)));
break;
case BETWEEN:
List<Object> between = new ArrayList<>((List<Object>)val);
list.add(cb.between(getExpression(attributeName, join, root).as((Class<? extends Comparable>) between.get(0).getClass()),
(Comparable) between.get(0), (Comparable) between.get(1)));
break;
default: break;
}
}
field.setAccessible(accessible);
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
int size = list.size();
return cb.and(list.toArray(new Predicate[size]));
}
@SuppressWarnings("unchecked")
private static <T, R> Expression<T> getExpression(String attributeName, Join join, Root<R> root) {
if (ObjectUtil.isNotEmpty(join)) {
return join.get(attributeName);
} else {
return root.get(attributeName);
}
}
private static boolean isBlank(final CharSequence cs) {
int strLen;
if (cs == null || (strLen = cs.length()) == 0) {
return true;
}
for (int i = 0; i < strLen; i++) {
if (!Character.isWhitespace(cs.charAt(i))) {
return false;
}
}
return true;
}
private static List<Field> getAllFields(Class<?> clazz, List<Field> fields) {
fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
if (clazz.getSuperclass() != null) {
getAllFields(clazz.getSuperclass(), fields);
}
return fields;
}
}
Мы можем использовать на уровне обслуживания Specification
для выполнения динамических запросов. Например, мы можем BookService
Добавьте метод для динамического запроса книг на основе условий запроса.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class BookService {
@Autowired
private BookRepository bookRepository;
public List<Book> findAllBooks(BookQueryCriteria criteria) {
Specification<Book> specification = (root, query, cb) -> QueryHelp.getPredicate(root, criteria, cb);
return bookRepository.findAll(specification);
}
}
С помощью описанных выше шагов мы реализовали простую систему запросов книг, которую можно создавать динамически на основе условий запроса, предоставленных пользователем. JPA Запрос。Такой подход не только улучшает кодизгибкостьи Ремонтопригодность,Также усовершенствована системаиз Масштабируемость。Specification
、CriteriaBuilder
и Predicate
да JPA При наличии мощных инструментов освоение их использования может значительно повысить эффективность нашей разработки.