Spring Boot中使用JPA構建動態查詢

banq發表於2024-06-03

在本文中,我們將探索一個靈活且可重複使用的框架,使開發人員能夠毫不費力地構建複雜的查詢。

動態查詢構建是現代應用程式開發的一個關鍵方面,尤其是在編譯時不知道搜尋條件的情況下。在本文中,讓我們深入探討使用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 (!<font>"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({ <font>"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;
    
    <font>// Getters and setters<i>
}

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

有了這些實體,讓我們來演示如何使用我們的動態查詢構建框架,根據特定的搜尋條件檢索使用者列表:

Criteria<User> criteria = new Criteria<>();
criteria.add(Restrictions.eq(<font>"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 標準查詢進行動態查詢構建,使開發人員能夠根據自己的特定需求建立複雜的查詢。透過利用本出版物中概述的框架,開發人員可以簡化構建動態查詢的過程,並提高應用程式的靈活性和效率。

相關文章