在开发过程中,使用JPA进行数据库查询是常见的任务之一。为了提高代码的可维护性和灵活性,可以借助nbsaas-boot框架进行适配,实现一套强大而灵活的JPA查询设计。本文将深入探讨核心代码和使用例子。
核心代码包含一个通用的查询方法 search,该方法接受一个 PageRequest 对象和一个用于转换的函数 convert,返回一个 PageResponse 对象。以下是该方法的主要步骤:
创建 Pageable 对象:根据传入的请求参数和排序信息创建一个 Pageable 对象,用于后续的JPA查询。
执行JPA查询:利用JPA的 findAll 方法执行查询,得到包含实体对象的 Page 对象。
转换结果:将查询结果通过转换函数 convert 转换为目标类型,并设置到 PageResponse 对象中。
返回结果:返回构建好的 PageResponse 对象。
SpecificationData 类实现了JPA的 Specification 接口,用于构建动态查询条件。在 toPredicate 方法中,通过反射获取请求对象的所有字段,然后根据字段上的 Search 注解构建查询条件,最终通过 criteriaBuilder 构建完整的 Predicate。
Search 注解是nbsaas-boot定义的一套查询注解,用于标识字段的查询条件,包括操作符、字段名、前缀、SQL 表达式和条件类型等信息。通过在实体类的字段上添加 Search 注解,可以灵活定义查询条件。
Operator 是查询操作的枚举,包括等于、不等于、大于、小于、大于等于、小于等于、模糊匹配等操作。通过在 Search 注解中指定操作符,可以控制查询的逻辑。
OperatorStrategy 接口定义了操作符策略,具体的操作符实现类如 EqStrategy、GtStrategy 等都实现了该接口,用于处理不同的操作符逻辑。
StrategyList 类维护了一个操作符和策略的映射关系,通过 getStrategy 方法可以动态获取对应操作符的策略实例。这种设计使得后续可以灵活扩展操作符的实现。
protected <T> PageResponse<T> search(PageRequest request, Function<Entity, T> convert) {
PageResponse<T> result = new PageResponse<>();
SpecificationData<Entity> data = new SpecificationData<>(request);
Pageable pageable = org.springframework.data.domain.PageRequest.of(request.getNo() - 1, request.getSize());
if (StringUtils.hasText(request.getSortField())){
pageable = org.springframework.data.domain.PageRequest.of(request.getNo() - 1, request.getSize(),getSortData(request));
}
Page<Entity> res = getJpaRepository().findAll(data, pageable);
if (!res.getContent().isEmpty()) {
List<T> list = res.getContent().stream().map(convert).collect(toList());
result.setData(list);
}
result.setSize(res.getSize());
result.setNo(res.getNumber());
result.setTotal(res.getTotalElements());
result.setTotalPage(res.getTotalPages());
return result;
}
package com.nbsaas.boot.jpa.data.core;
import com.nbsaas.boot.jpa.data.strategy.OperatorStrategy;
import com.nbsaas.boot.jpa.data.strategy.StrategyList;
import com.nbsaas.boot.rest.filter.Condition;
import com.nbsaas.boot.rest.filter.Search;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.util.StringUtils;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class SpecificationData<T> implements Specification<T> {
private final Object request;
public SpecificationData(Object request) {
this.request = request;
}
@Override
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
List<Field> fieldList = new ArrayList<Field>();
Class<?> temp = request.getClass();
while (temp != Object.class) {
Field[] fields = temp.getDeclaredFields();
fieldList.addAll(Arrays.asList(fields));
temp = temp.getSuperclass();
}
List<Predicate> predicates = new ArrayList<>();
for (Field field : fieldList) {
Search item = field.getAnnotation(Search.class);
if (item != null) {
field.setAccessible(true);
try {
Object object = field.get(request);
if (object == null) {
continue;
}
if (object instanceof String) {
String oString = (String) object;
if (!StringUtils.hasText(oString)) {
continue;
}
object = oString.trim();
}
Predicate predicate = null;
OperatorStrategy operatorStrategy = StrategyList.getStrategy(item.operator());
if (operatorStrategy != null) {
predicate = operatorStrategy.handle(criteriaBuilder, root, item.name(), object);
}
if (predicate != null) {
if (item.condition()== Condition.AND){
predicates.add(predicate);
}else{
predicates.add(criteriaBuilder.or(predicate));
}
}
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
}
}
搜索注解
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Search {
/**
* 操作符
*/
Operator operator() default Operator.eq;
/**
* 字段名
*/
String name() default "";
String prefix() default "";
String sql() default "";
Condition condition() default Condition.AND;
}
查询操作枚举
public enum Operator {
eq,//等于
ne,//不等于
gt,//大于
lt,//小于
ge,//大于等于
le,
like,//%key%匹配
likePrefix,//key%匹配
likeSuffix,//%key匹配
in,//在什么范围
notIn,//不在什么范围
isNull,//为空
isNotNull,//不为空
inSql,//mp专用
notInSql,//mp专用
apply,//mp专用
between;//范围值
Operator() {
}
public Operator fromString(String value) {
return valueOf(value.toLowerCase());
}
}
/**
* 操作符策略
*/
public interface OperatorStrategy {
Predicate handle(CriteriaBuilder criteriaBuilder, Root<?> root, String field, Object object);
}
public class EqStrategy implements OperatorStrategy {
@Override
public Predicate handle(CriteriaBuilder criteriaBuilder, Root<?> root, String field, Object object) {
return criteriaBuilder.equal(PathUtils.getPath(root, field), object);
}
}
public class GtStrategy implements OperatorStrategy {
@Override
public Predicate handle(CriteriaBuilder criteriaBuilder, Root<?> root, String field, Object object) {
if (object instanceof Number) {
return criteriaBuilder.gt(PathUtils.getPath(root,field), (Number) object);
} else if (object instanceof Comparable) {
return criteriaBuilder.greaterThan(PathUtils.getPath(root,field), (Comparable) object);
}
return null;
}
}
其他的包括BetweenStrategy,InStrategy,IsNotNullStrategy,IsNullStrategy,LeStrategy,likePrefixStrategy
LikeStrategy,likeSuffixStrategy,LtStrategy。
通过策略设计,后期可以动态扩展操作符
public class StrategyList {
private static final Map<Operator, OperatorStrategy> strategyMap = new HashMap<>();
static {
strategyMap.put(Operator.eq, new EqStrategy());
strategyMap.put(Operator.ge, new GeStrategy());
strategyMap.put(Operator.ne, new NeStrategy());
strategyMap.put(Operator.gt, new GtStrategy());
strategyMap.put(Operator.in, new InStrategy());
strategyMap.put(Operator.isNotNull, new IsNotNullStrategy());
strategyMap.put(Operator.isNull, new IsNullStrategy());
strategyMap.put(Operator.le, new LeStrategy());
strategyMap.put(Operator.lt, new LtStrategy());
strategyMap.put(Operator.like, new LikeStrategy());
strategyMap.put(Operator.likeSuffix, new likeSuffixStrategy());
strategyMap.put(Operator.likePrefix, new likePrefixStrategy());
strategyMap.put(Operator.between, new BetweenStrategy());
}
public static OperatorStrategy getStrategy(Operator operator) {
if (operator == null) {
return null;
}
return strategyMap.get(operator);
}
}
package com.nbsaas.boot.product.api.domain.request;
import com.nbsaas.boot.rest.filter.Operator;
import com.nbsaas.boot.rest.filter.Search;
import com.nbsaas.boot.rest.request.PageRequest;
import lombok.*;
import java.io.Serializable;
import java.util.Date;
import java.math.BigDecimal;
import com.nbsaas.boot.rest.enums.StoreState;
/**
* 搜索bean
*/
@Getter
@Setter
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class ProductSearch extends PageRequest implements Serializable {
/**
* 序列化参数
*/
private static final long serialVersionUID = 1L;
@Search(name = "shop.id", operator = Operator.eq)
private Long shop;
@Search(name = "name", operator = Operator.like)
private String name;
@Search(name = "storeState", operator = Operator.eq)
private StoreState storeState;
/**
*
**/
@Search(name = "summary", operator = Operator.like)
private String summary;
/**
*
**/
@Search(name = "thumbnail", operator = Operator.like)
private String thumbnail;
/**
*
**/
@Search(name = "barCode", operator = Operator.like)
private String barCode;
/**
*
**/
@Search(name = "stockNum", operator = Operator.eq)
private Long stockNum;
/**
*
**/
@Search(name = "realStock", operator = Operator.eq)
private Long realStock;
/**
*
**/
@Search(name = "logo", operator = Operator.like)
private String logo;
/**
* 主键id
**/
@Search(name = "id", operator = Operator.eq)
private Long id;
}
这样就可以实现查询了
@RequestMapping("/search")
public PageResponse <ProductSimple> search(ProductSearch request) {
request.setStoreState(StoreState.normal);
return productApi.search(request);
}
通过借助nbsaas-boot框架,我们构建了一套强大而灵活的JPA查询设计。核心代码中的通用查询方法、JPA Specification、查询注解、操作符枚举和操作符策略设计,为开发者提供了便捷而高效的数据库查询方案。使用例子展示了如何在实际项目中应用这些设计,使得查询操作变得简单而灵活。这种设计模式不仅提高了代码的可读性和可维护性,还为未来的功能扩展提供了良好的扩展性。