nbsaas-boot适配jpa的查询设计

发布时间:2024年01月19日

在开发过程中,使用JPA进行数据库查询是常见的任务之一。为了提高代码的可维护性和灵活性,可以借助nbsaas-boot框架进行适配,实现一套强大而灵活的JPA查询设计。本文将深入探讨核心代码和使用例子。

核心代码解析

通用查询方法 - search

核心代码包含一个通用的查询方法 search,该方法接受一个 PageRequest 对象和一个用于转换的函数 convert,返回一个 PageResponse 对象。以下是该方法的主要步骤:

创建 Pageable 对象:根据传入的请求参数和排序信息创建一个 Pageable 对象,用于后续的JPA查询。

执行JPA查询:利用JPA的 findAll 方法执行查询,得到包含实体对象的 Page 对象。

转换结果:将查询结果通过转换函数 convert 转换为目标类型,并设置到 PageResponse 对象中。

返回结果:返回构建好的 PageResponse 对象。

JPA Specification - SpecificationData

SpecificationData 类实现了JPA的 Specification 接口,用于构建动态查询条件。在 toPredicate 方法中,通过反射获取请求对象的所有字段,然后根据字段上的 Search 注解构建查询条件,最终通过 criteriaBuilder 构建完整的 Predicate。

查询注解 - Search

Search 注解是nbsaas-boot定义的一套查询注解,用于标识字段的查询条件,包括操作符、字段名、前缀、SQL 表达式和条件类型等信息。通过在实体类的字段上添加 Search 注解,可以灵活定义查询条件。

查询操作符 - Operator

Operator 是查询操作的枚举,包括等于、不等于、大于、小于、大于等于、小于等于、模糊匹配等操作。通过在 Search 注解中指定操作符,可以控制查询的逻辑。

操作符策略设计 - OperatorStrategy

OperatorStrategy 接口定义了操作符策略,具体的操作符实现类如 EqStrategy、GtStrategy 等都实现了该接口,用于处理不同的操作符逻辑。

操作符策略列表 - StrategyList

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]));
    }
}

nbsaas-boot定义了一套完整的查询注解

搜索注解

@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。
通过策略设计,后期可以动态扩展操作符

通过StrategyList动态获取操作符

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、查询注解、操作符枚举和操作符策略设计,为开发者提供了便捷而高效的数据库查询方案。使用例子展示了如何在实际项目中应用这些设计,使得查询操作变得简单而灵活。这种设计模式不仅提高了代码的可读性和可维护性,还为未来的功能扩展提供了良好的扩展性。

文章来源:https://blog.csdn.net/u010362741/article/details/135669113
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。