Spring Boot中使用JPA构建动态查询

在本文中,我们将探索一个灵活且可重复使用的框架,使开发人员能够毫不费力地构建复杂的查询。

动态查询构建是现代应用程序开发的一个关键方面,尤其是在编译时不知道搜索条件的情况下。在本文中,让我们深入探讨使用JPA 条件查询在Spring Boot 应用程序中构建动态查询的世界。我们将探索一个灵活且可重用的框架,使开发人员能够轻松构建复杂的查询。

标准接口

  • Criteria接口是我们框架的基础。它扩展Specification<T>并提供了构建动态查询的标准化方法。
  • 通过实现该toPredicate方法,Criteria接口能够根据指定的标准构建谓词。


import java.util.ArrayList;
import java.util.List;

import org.springframework.data.jpa.domain.Specification;

import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;

public class Criteria<T> implements Specification<T> {

    private static final long serialVersionUID = 1L;
    
    private transient List<Criterion> criterions = new ArrayList<>();

    @Override
    public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
        if (!criterions.isEmpty()) {
            List<Predicate> predicates = new ArrayList<>();
            for (Criterion c : criterions) {
                predicates.add(c.toPredicate(root, query, builder));
            }

            if (!predicates.isEmpty()) {
                return builder.and(predicates.toArray(new Predicate[predicates.size()]));
            }
        }
        return builder.conjunction();
    }

    public void add(Criterion criterion) {
        if (criterion != null) {
            criterions.add(criterion);
        }
    }

}

标准接口
标准接口定义了构建单个谓词的契约。它包括 toPredicate 方法,该方法由不同的类实现,用于创建特定的谓词,如等于、不等于、同类等。

public interface Criterion {
    public enum Operator {
        EQ, IGNORECASEEQ, NE, LIKE, GT, LT, GTE, LTE, AND, OR, ISNULL
    }

    public Predicate toPredicate(Root<?> root, CriteriaQuery<?> query, CriteriaBuilder builder);
}

逻辑表达式类

  • LogicalExpression 类便于使用 AND 和 OR 等逻辑运算符组合多个条件。
  • 通过实现 toPredicate 方法,该类允许开发人员通过将简单的条件连锁在一起来创建复杂的查询条件。

public class LogicalExpression implements Criterion {  
    private Criterion[] criterion;  
    private Operator operator;      
  
    public LogicalExpression(Criterion[] criterions, Operator operator) {  
        this.criterion = criterions;  
        this.operator = operator;  
    }  
  
    @Override
    public Predicate toPredicate(Root<?> root, CriteriaQuery<?> query, CriteriaBuilder builder) {  
        List<Predicate> predicates = new ArrayList<>();  
        for(int i=0;i<this.criterion.length;i++){  
            predicates.add(this.criterion[i].toPredicate(root, query, builder));  
        }  
        
        if(null != operator && operator.equals(Criterion.Operator.OR)) {
            return builder.or(predicates.toArray(new Predicate[predicates.size()]));  
        } 
        
        return null;
    }  
}

限制类

  • Restrictions 类提供了一组静态方法,用于创建 SimpleExpression 和 LogicalExpression 实例。
  • 这些方法提供了创建简单和复杂条件的便捷方法,使开发人员更容易构建动态查询。

public class Restrictions {
    private Restrictions() {
    }

    public static SimpleExpression eq(String fieldName, Object value, boolean ignoreNull) {
        if (ignoreNull && (ObjectUtils.isEmpty(value)))
            return null;
        return new SimpleExpression(fieldName, value, Operator.EQ);
    }

    public static SimpleExpression ne(String fieldName, Object value, boolean ignoreNull) {
        if (ignoreNull && (ObjectUtils.isEmpty(value)))
            return null;
        return new SimpleExpression(fieldName, value, Operator.NE);
    }

    public static SimpleExpression like(String fieldName, String value, boolean ignoreNull) {
        if (ignoreNull && (ObjectUtils.isEmpty(value)))
            return null;
        return new SimpleExpression(fieldName, value.toUpperCase(), Operator.LIKE);
    }

    public static SimpleExpression gt(String fieldName, Object value, boolean ignoreNull) {
        if (ignoreNull && (ObjectUtils.isEmpty(value)))
            return null;
        return new SimpleExpression(fieldName, value, Operator.GT);
    }

    public static SimpleExpression lt(String fieldName, Object value, boolean ignoreNull) {
        if (ignoreNull && (ObjectUtils.isEmpty(value)))
            return null;
        return new SimpleExpression(fieldName, value, Operator.LT);
    }

    public static SimpleExpression gte(String fieldName, Object value, boolean ignoreNull) {
        if (ignoreNull && (ObjectUtils.isEmpty(value)))
            return null;
        return new SimpleExpression(fieldName, value, Operator.GTE);
    }

    public static SimpleExpression lte(String fieldName, Object value, boolean ignoreNull) {
        if (ignoreNull && (ObjectUtils.isEmpty(value)))
            return null;
        return new SimpleExpression(fieldName, value, Operator.LTE);
    }

    public static SimpleExpression isNull(String fieldName, boolean ignoreNull) {
        if (ignoreNull)
            return null;
        return new SimpleExpression(fieldName, null, Operator.ISNULL);
    }

    public static LogicalExpression and(Criterion... criterions) {
        return new LogicalExpression(criterions, Operator.AND);
    }

    public static LogicalExpression or(Criterion... criterions) {
        return new LogicalExpression(criterions, Operator.OR);
    }

    public static <E> LogicalExpression in(String fieldName, Collection<E> value, boolean ignoreNull) {
        if (ignoreNull && CollectionUtils.isEmpty(value))
            return null;

        SimpleExpression[] ses = new SimpleExpression[value.size()];
        int i = 0;
        for (Object obj : value) {
            if(obj instanceof String) {
                ses[i] = new SimpleExpression(fieldName, String.valueOf(obj), Operator.IGNORECASEEQ);
            } else {
                ses[i] = new SimpleExpression(fieldName, obj, Operator.EQ);
            }
            i++;
        }
        return new LogicalExpression(ses, Operator.OR);
    }

    public static Long convertToLong(Object o) {
        String stringToConvert = String.valueOf(o);
        if (!"null".equals(stringToConvert)) {
            return Long.parseLong(stringToConvert);
        } else {
            return Long.valueOf(0);
        }
    }
}


SimpleExpression 类

  • SimpleExpression 类表示带有各种运算符(如等于、不等于、同类、大于、小于等)的简单表达式。
  • 通过实现 toPredicate 方法,该类可将简单表达式转换为 JPA 标准谓词,从而实现精确的查询构造。
  • SimpleExpression 类表示带有各种操作符(如等于、不等于、同类、大于、小于等)的简单表达式。
  • 通过实现 toPredicate 方法,该类可将简单表达式转换为 JPA 条件谓词,从而实现精确的查询构造。

public class SimpleExpression implements Criterion {
    private String fieldName;
    private Object value;
    private Operator operator;

    protected SimpleExpression(String fieldName, Object value, Operator operator) {
        this.fieldName = fieldName;
        this.value = value;
        this.operator = operator;
    }

    @Override
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public Predicate toPredicate(Root<?> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
        Path expression = null;
        if (fieldName.contains(
".")) {
            String[] names = StringUtils.split(fieldName,
".");
            if(names!=null && names.length>0) {
                expression = root.get(names[0]);
                for (int i = 1; i < names.length; i++) {
                    expression = expression.get(names[i]);
                }
            }
        } else {
            expression = root.get(fieldName);
        }

        switch (operator) {
            case EQ:
                return builder.equal(expression, value);
            case IGNORECASEEQ:
                return builder.equal(builder.upper(expression), value.toString().toUpperCase());
            case NE:
                return builder.notEqual(expression, value);
            case LIKE:
                return builder.like(builder.upper(expression), value.toString().toUpperCase() +
"%");
            case LT:
                return builder.lessThan(expression, (Comparable) value);
            case GT:
                return builder.greaterThan(expression, (Comparable) value);
            case LTE:
                return builder.lessThanOrEqualTo(expression, (Comparable) value);
            case GTE:
                return builder.greaterThanOrEqualTo(expression, (Comparable) value);
            case ISNULL:
                return builder.isNull(expression);
            default:
                return null;
        }
    }
}

使用示例
假设我们在 Spring Boot 应用程序中定义了一个 User 实体和一个相应的 UserRepository 接口:

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private int age;
    private double salary;
    
    // Getters and setters
}

public interface UserRepository extends JpaRepository<User, Long> {
}  

有了这些实体,让我们来演示如何使用我们的动态查询构建框架,根据特定的搜索条件检索用户列表:

Criteria<User> criteria = new Criteria<>();
criteria.add(Restrictions.eq("age", 25, true));
criteria.add(Restrictions.like(
"name", "John", true));
criteria.add(Restrictions.or(
    Restrictions.gt(
"salary", 50000, true),
    Restrictions.isNull(
"salary", null, false)
));

List<User> users = userRepository.findAll(criteria);

在本例中,我们使用标准接口和框架提供的各种限制条件构建了一个动态查询。我们指定的条件包括年龄等于 25 岁、姓名包含 "John"、薪水大于 50000 或为空。最后,我们使用 UserRepository 执行查询并检索匹配的用户。

结论
在 Spring Boot 应用程序中使用 JPA 标准查询进行动态查询构建,使开发人员能够根据自己的特定需求创建复杂的查询。通过利用本出版物中概述的框架,开发人员可以简化构建动态查询的过程,并提高应用程序的灵活性和效率。