欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot封裝實(shí)現(xiàn)分頁(yè)查詢(xún)工具

 更新時(shí)間:2025年06月20日 11:14:54   作者:惜鳥(niǎo)  
在Spring Boot項(xiàng)目中使用MyBatis進(jìn)行分頁(yè)查詢(xún),通常有兩種主流方式,RowBounds和PageHelper,下面小編就來(lái)和大家詳細(xì)介紹一下如何使用二者實(shí)現(xiàn)分頁(yè)查詢(xún)工具吧

概述

在Spring Boot項(xiàng)目中使用MyBatis進(jìn)行分頁(yè)查詢(xún),通常有兩種主流方式:

  • 使用MyBatis內(nèi)置的RowBounds進(jìn)行內(nèi)存分頁(yè)(不推薦,數(shù)據(jù)量大時(shí)性能差)
  • 使用分頁(yè)插件,如PageHelper

使用PageHelper可能遇到的一些問(wèn)題

PageHelper 是一個(gè)非常流行的 MyBatis 分頁(yè)插件,但它也有一些潛在的缺點(diǎn)和需要注意的地方。以下是在使用 PageHelper 時(shí)可能遇到的一些問(wèn)題:

1.線(xiàn)程安全問(wèn)題

問(wèn)題描述PageHelper.startPage() 方法使用了 ThreadLocal 來(lái)保存分頁(yè)參數(shù)。如果在同一個(gè)線(xiàn)程中多次調(diào)用 startPage() 而沒(méi)有及時(shí)清理(比如在 finally 塊中調(diào)用 PageHelper.clearPage()),或者線(xiàn)程被復(fù)用(如線(xiàn)程池場(chǎng)景),可能導(dǎo)致分頁(yè)參數(shù)混亂。

2.對(duì)復(fù)雜 SQL 的支持有限

問(wèn)題描述:PageHelper 通過(guò)攔截 SQL 并重寫(xiě)來(lái)實(shí)現(xiàn)分頁(yè)。對(duì)于特別復(fù)雜的 SQL(例如包含多個(gè)嵌套子查詢(xún)、UNION 等),自動(dòng)生成的 count 查詢(xún)語(yǔ)句可能會(huì)出錯(cuò),導(dǎo)致分頁(yè)結(jié)果不正確。

3.性能問(wèn)題

Count 查詢(xún)效率:默認(rèn)情況下,PageHelper 會(huì)執(zhí)行一個(gè) count 查詢(xún)獲取總記錄數(shù)。如果表數(shù)據(jù)量非常大(上千萬(wàn)),這個(gè) count 操作可能很慢(尤其是沒(méi)有合適索引時(shí))。

4.與其它攔截器沖突

問(wèn)題描述:如果項(xiàng)目中同時(shí)使用了多個(gè) MyBatis 攔截器(如數(shù)據(jù)權(quán)限攔截器、加解密攔截器等),攔截器的執(zhí)行順序可能影響 PageHelper 的正常工作(因?yàn)榉猪?yè)依賴(lài)于改寫(xiě) SQL)。確保 PageHelper 是最后一個(gè)執(zhí)行的攔截器(可以通過(guò)調(diào)整攔截器添加順序?qū)崿F(xiàn))。

5.對(duì)異步/多線(xiàn)程支持不友好

問(wèn)題描述:由于依賴(lài) ThreadLocal,如果在異步任務(wù)(如 @Async)或多線(xiàn)程環(huán)境中使用 PageHelper,分頁(yè)參數(shù)可能無(wú)法正確傳遞到子線(xiàn)程。

6.返回對(duì)象過(guò)于臃腫

問(wèn)題描述PageInfo 對(duì)象包含大量分頁(yè)信息(如總頁(yè)數(shù)、導(dǎo)航頁(yè)碼列表等),但實(shí)際業(yè)務(wù)中可能只需要部分字段(如當(dāng)前頁(yè)數(shù)據(jù)、總記錄數(shù))。

7.設(shè)計(jì)耦合

問(wèn)題描述:分頁(yè)邏輯侵入業(yè)務(wù)代碼(Service 層中顯式調(diào)用 PageHelper.startPage()),違反了分層設(shè)計(jì)的純粹性。

建議:PageHelper 適合中小型項(xiàng)目的快速開(kāi)發(fā),但在高并發(fā)、大數(shù)據(jù)量、復(fù)雜SQL場(chǎng)景下需謹(jǐn)慎使用,必要時(shí)采用更可控的分頁(yè)方案。

自定義分頁(yè)查詢(xún)工具

我們可以在Spring Boot項(xiàng)目中不使用PageHelper,而是自己封裝一個(gè)分頁(yè)查詢(xún)工具。主要思路如下:

  • 定義一個(gè)分頁(yè)請(qǐng)求參數(shù)類(lèi),包含頁(yè)碼和每頁(yè)數(shù)量。
  • 定義一個(gè)分頁(yè)結(jié)果類(lèi),包含數(shù)據(jù)列表、總記錄數(shù)、總頁(yè)數(shù)、當(dāng)前頁(yè)碼、每頁(yè)數(shù)量等信息。
  • 定義一個(gè)分頁(yè)查詢(xún)工具:先查詢(xún)總數(shù),再查詢(xún)當(dāng)前頁(yè)數(shù)據(jù),然后封裝成分頁(yè)結(jié)果對(duì)象。
  • 使用MyBatis Dynamic SQL自定義復(fù)雜分頁(yè)查詢(xún)邏輯:一個(gè)用于查詢(xún)符合條件的總記錄數(shù),一個(gè)用于查詢(xún)當(dāng)前頁(yè)的數(shù)據(jù)(使用數(shù)據(jù)庫(kù)的分頁(yè)語(yǔ)法,如MySQL的LIMIT)。

下面我們一步步實(shí)現(xiàn):

步驟1:創(chuàng)建分頁(yè)請(qǐng)求參數(shù)類(lèi)(PageRequest)

步驟2:創(chuàng)建分頁(yè)結(jié)果類(lèi)(PageResult)

步驟3:創(chuàng)建分頁(yè)查詢(xún)工具(PaginationUtils)

步驟4:在Mapper接口中使用MyBatis Dynamic SQL自定義復(fù)雜分頁(yè)查詢(xún)邏輯

步驟5:在Service層調(diào)用Mapper的兩個(gè)方法,并封裝PageResult

步驟6:在Controller中接收分頁(yè)參數(shù),調(diào)用Service方法

分頁(yè)查詢(xún)具體代碼實(shí)現(xiàn)

1. 分頁(yè)請(qǐng)求參數(shù)類(lèi)(PageRequest)

import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Pattern;
import java.util.List;
import java.util.Set;
/**
 * 分頁(yè)請(qǐng)求參數(shù)封裝類(lèi)
 */
public class PageRequest {
    /**
     * 默認(rèn)第一頁(yè)
     */
    public static final int DEFAULT_PAGE_NUM = 1;
    /**
     * 默認(rèn)每頁(yè)10條
     */
    public static final int DEFAULT_PAGE_SIZE = 10;
    /**
     * 默認(rèn)排序方向 - 升序
     */
    public static final String DEFAULT_ORDER = "desc";
    /**
     * 最大允許的每頁(yè)記錄數(shù)
     */
    public static final int MAX_PAGE_SIZE = 1000;
    /**
     * 當(dāng)前頁(yè)碼(從1開(kāi)始)
     */
    @Min(value = 1, message = "頁(yè)碼不能小于1")
    private int pageNum = DEFAULT_PAGE_NUM;
    /**
     * 每頁(yè)記錄數(shù)
     */
    @Min(value = 1, message = "每頁(yè)數(shù)量不能小于1")
    @Max(value = MAX_PAGE_SIZE, message = "每頁(yè)數(shù)量不能超過(guò)" + MAX_PAGE_SIZE)
    private int pageSize = DEFAULT_PAGE_SIZE;
    /**
     * 排序字段
     */
    private String sort;
    /**
     * 排序方向
     * asc: 升序
     * desc: 降序
     */
    @Pattern(regexp = "asc|desc", message = "排序方向必須是asc或desc")
    private String order = DEFAULT_ORDER;
    // 無(wú)參構(gòu)造器
    public PageRequest() {
    }
    /**
     * 帶頁(yè)碼和每頁(yè)數(shù)量的構(gòu)造器
     *
     * @param pageNum  當(dāng)前頁(yè)碼
     * @param pageSize 每頁(yè)數(shù)量
     */
    public PageRequest(int pageNum, int pageSize) {
        this.pageNum = pageNum;
        this.pageSize = pageSize;
    }
    /**
     * 帶所有參數(shù)的構(gòu)造器
     *
     * @param pageNum  當(dāng)前頁(yè)碼
     * @param pageSize 每頁(yè)數(shù)量
     * @param sort     排序字段
     * @param order    排序方向
     */
    public PageRequest(int pageNum, int pageSize, String sort, String order) {
        this.pageNum = pageNum;
        this.pageSize = pageSize;
        this.sort = sort;
        this.order = order;
    }
    /**
     * 計(jì)算偏移量(用于數(shù)據(jù)庫(kù)分頁(yè)查詢(xún))
     *
     * @return 當(dāng)前頁(yè)的起始位置
     */
    public int getOffset() {
        return (pageNum - 1) * pageSize;
    }
    /**
     * 驗(yàn)證排序字段是否在允許的列表中
     *
     * @param allowedFields 允許的排序字段集合
     * @return 如果排序字段有效返回true,否則返回false
     */
    public boolean isSortValid(Set<String> allowedFields) {
        if (sort == null || sort.isEmpty()) {
            return true;
        }
        return allowedFields.contains(sort);
    }
    /**
     * 驗(yàn)證排序字段是否在允許的列表中,無(wú)效時(shí)拋出異常
     *
     * @param allowedFields 允許的排序字段集合
     * @param errorMessage  錯(cuò)誤信息
     * @throws IllegalArgumentException 如果排序字段無(wú)效
     */
    public void validateSort(List<String> allowedFields, String errorMessage) {
        if (sort != null && !sort.isEmpty() && !allowedFields.contains(sort)) {
            throw new IllegalArgumentException(errorMessage);
        }
    }
    public int getPageNum() {
        return pageNum;
    }
    public void setPageNum(int pageNum) {
        this.pageNum = pageNum;
    }
    public int getPageSize() {
        return pageSize;
    }
    public void setPageSize(int pageSize) {
        this.pageSize = pageSize;
    }
    public String getSort() {
        return sort;
    }
    public void setSort(String sort) {
        this.sort = sort;
    }
    public String getOrder() {
        return order;
    }
    public void setOrder(String order) {
        this.order = order;
    }
}

2. 分頁(yè)結(jié)果類(lèi)(PageResult)

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
public class PageResult<T> {
    private final int pageNum;        // 當(dāng)前頁(yè)碼
    private final int pageSize;        // 每頁(yè)數(shù)量
    private final long total;      // 總記錄數(shù)
    private final int totalPage;       // 總頁(yè)數(shù)
    private final List<T> data;    // 當(dāng)前頁(yè)數(shù)據(jù)
    private final String sort;     // 排序字段
    private final String order;    // 排序方向
    /**
     * 構(gòu)造函數(shù)
     *
     * @param pageRequest 分頁(yè)請(qǐng)求
     * @param total       總記錄數(shù)
     * @param data        當(dāng)前頁(yè)數(shù)據(jù)
     */
    public PageResult(PageRequest pageRequest, long total, List<T> data) {
        this.pageNum = pageRequest.getPageNum();
        this.pageSize = pageRequest.getPageSize();
        this.sort = pageRequest.getSort();
        this.order = pageRequest.getOrder();
        this.total = total;
        this.totalPage = calculateTotalPage(total, pageRequest.getPageSize());
        this.data = data;
    }
    /**
     * 構(gòu)造函數(shù)
     *
     * @param pageNum  當(dāng)前頁(yè)碼
     * @param pageSize  每頁(yè)數(shù)量
     * @param total 總記錄數(shù)
     * @param data  當(dāng)前頁(yè)數(shù)據(jù)
     * @param sort  排序字段
     * @param order 排序方向
     */
    public PageResult(int pageNum, int pageSize, long total, List<T> data, String sort, String order) {
        this.pageNum = pageNum;
        this.pageSize = pageSize;
        this.total = total;
        this.data = data != null ? data : Collections.emptyList();
        this.sort = sort;
        this.order = order;
        // 計(jì)算總頁(yè)數(shù)
        this.totalPage = calculateTotalPage(total, pageSize);
    }
    /**
     * 計(jì)算總頁(yè)數(shù)
     *
     * @param total 總記錄數(shù)
     * @param size  每頁(yè)數(shù)量
     * @return 總頁(yè)數(shù)
     */
    private int calculateTotalPage(long total, int size) {
        if (size <= 0) return 0;
        return (int) Math.ceil((double) total / size);
    }
    // ================ 實(shí)用靜態(tài)方法 ================ //
    /**
     * 創(chuàng)建空的分頁(yè)結(jié)果
     *
     * @param <T> 數(shù)據(jù)類(lèi)型
     * @return 空的分頁(yè)結(jié)果
     */
    public static <T> PageResult<T> empty() {
        return new PageResult<>(1, 0, 0, Collections.emptyList(), null, null);
    }
    /**
     * 基于 PageRequest 創(chuàng)建空的分頁(yè)結(jié)果
     *
     * @param pageRequest 分頁(yè)請(qǐng)求
     * @param <T>         數(shù)據(jù)類(lèi)型
     * @return 空的分頁(yè)結(jié)果
     */
    public static <T> PageResult<T> empty(PageRequest pageRequest) {
        return new PageResult<>(
                pageRequest.getPageNum(),
                pageRequest.getPageSize(),
                0,
                Collections.emptyList(),
                pageRequest.getSort(),
                pageRequest.getOrder()
        );
    }
    /**
     * 創(chuàng)建單頁(yè)結(jié)果(適用于數(shù)據(jù)量小的情況)
     *
     * @param data 所有數(shù)據(jù)
     * @param <T>  數(shù)據(jù)類(lèi)型
     * @return 單頁(yè)結(jié)果
     */
    public static <T> PageResult<T> singlePage(List<T> data) {
        long total = data != null ? data.size() : 0;
        return new PageResult<>(1, (int) total, total, data, null, null);
    }
    /**
     * 創(chuàng)建分頁(yè)結(jié)果(基于 PageRequest)
     *
     * @param pageRequest 分頁(yè)請(qǐng)求
     * @param total       總記錄數(shù)
     * @param data        當(dāng)前頁(yè)數(shù)據(jù)
     * @param <T>         數(shù)據(jù)類(lèi)型
     * @return 分頁(yè)結(jié)果
     */
    public static <T> PageResult<T> of(PageRequest pageRequest, long total, List<T> data) {
        return new PageResult<>(
                pageRequest.getPageNum(),
                pageRequest.getPageSize(),
                total,
                data,
                pageRequest.getSort(),
                pageRequest.getOrder()
        );
    }
    /**
     * 轉(zhuǎn)換分頁(yè)結(jié)果的數(shù)據(jù)類(lèi)型
     *
     * @param source 源分頁(yè)結(jié)果
     * @param mapper 數(shù)據(jù)轉(zhuǎn)換函數(shù)
     * @param <T>    源數(shù)據(jù)類(lèi)型
     * @param <R>    目標(biāo)數(shù)據(jù)類(lèi)型
     * @return 轉(zhuǎn)換后的分頁(yè)結(jié)果
     */
    public static <T, R> PageResult<R> map(PageResult<T> source, Function<T, R> mapper) {
        if (source == null || mapper == null) {
            throw new IllegalArgumentException("Source and mapper must not be null");
        }
        List<R> mappedData = source.getData().stream()
                .map(mapper)
                .collect(Collectors.toList());
        return new PageResult<>(
                source.getPageNum(),
                source.getPageSize(),
                source.getTotal(),
                mappedData,
                source.getSort(),
                source.getOrder()
        );
    }
    /**
     * 合并兩個(gè)分頁(yè)結(jié)果(適用于并行查詢(xún)場(chǎng)景)
     *
     * @param result1  第一個(gè)分頁(yè)結(jié)果
     * @param result2  第二個(gè)分頁(yè)結(jié)果
     * @param combiner 數(shù)據(jù)合并函數(shù)
     * @param <T>      第一個(gè)結(jié)果的數(shù)據(jù)類(lèi)型
     * @param <U>      第二個(gè)結(jié)果的數(shù)據(jù)類(lèi)型
     * @param <R>      合并后的數(shù)據(jù)類(lèi)型
     * @return 合并后的分頁(yè)結(jié)果
     */
    public static <T, U, R> PageResult<R> combine(
            PageResult<T> result1,
            PageResult<U> result2,
            BiFunction<T, U, R> combiner) {
        // 驗(yàn)證分頁(yè)信息是否一致
        if (result1.getPageNum() != result2.getPageNum() ||
                result1.getPageSize() != result2.getPageSize() ||
                result1.getTotal() != result2.getTotal()) {
            throw new IllegalArgumentException("Page results are not compatible for combination");
        }
        // 驗(yàn)證數(shù)據(jù)數(shù)量是否一致
        if (result1.getData().size() != result2.getData().size()) {
            throw new IllegalArgumentException("Data lists have different sizes");
        }
        // 合并數(shù)據(jù)
        List<R> combinedData = new ArrayList<>();
        for (int i = 0; i < result1.getData().size(); i++) {
            R combined = combiner.apply(
                    result1.getData().get(i),
                    result2.getData().get(i)
            );
            combinedData.add(combined);
        }
        return new PageResult<>(
                result1.getPageNum(),
                result1.getPageSize(),
                result1.getTotal(),
                combinedData,
                result1.getSort(),
                result1.getOrder()
        );
    }
    public int getPageNum() {
        return pageNum;
    }
    public int getPageSize() {
        return pageSize;
    }
    public long getTotal() {
        return total;
    }
    public int getTotalPage() {
        return totalPage;
    }
    public List<T> getData() {
        return data;
    }
    public String getSort() {
        return sort;
    }
    public String getOrder() {
        return order;
    }
}

3. 創(chuàng)建分頁(yè)查詢(xún)工具(PaginationUtils)

import java.util.List;
import java.util.function.Supplier;
public class PaginationUtils {
    /**
     * 執(zhí)行分頁(yè)查詢(xún)(使用PageRequest對(duì)象)
     *
     * @param pageRequest   分頁(yè)請(qǐng)求(包含頁(yè)碼、大小、排序等信息)
     * @param countFunction 查詢(xún)總數(shù)的函數(shù)
     * @param dataFunction  查詢(xún)數(shù)據(jù)的函數(shù)
     * @return 分頁(yè)結(jié)果
     */
    public static <T> PageResult<T> paginate(PageRequest pageRequest,
                                             Supplier<Long> countFunction,
                                             Supplier<List<T>> dataFunction) {
        // 查詢(xún)總數(shù)
        long total = countFunction.get();
        // 如果沒(méi)有數(shù)據(jù),直接返回空結(jié)果
        if (total == 0) {
            return PageResult.empty(pageRequest);
        }
        // 查詢(xún)當(dāng)前頁(yè)數(shù)據(jù)
        List<T> data = dataFunction.get();
        return new PageResult<>(pageRequest, total, data);
    }
}

4. Mapper接口示例(使用MyBatis Dynamic SQL)

當(dāng)進(jìn)行JOIN或復(fù)雜子查詢(xún)時(shí),查詢(xún)結(jié)果通常涉及多個(gè)實(shí)體,因此需要自定義結(jié)果映射。MyBatis Dynamic SQL本身不處理結(jié)果映射,你需要:

  • 使用注解:在Mapper接口的方法上使用@Results@Result注解定義映射關(guān)系。
  • 使用XML:在Mapper XML文件中定義<resultMap>

例如,對(duì)于規(guī)則和規(guī)則版本(一對(duì)多)的JOIN查詢(xún),結(jié)果封裝到一個(gè)DTO(Data Transfer Object)中:

import java.util.Date;

public class RuleWithLatestVersionDTO {
    private Long id;
    private String ruleId;
    private String name;
    private String domain;
    private Integer latestVersion;
    private String versionName;
    private String versionStatus;
    private Date versionModifiedDate;

    // getters and setters

}

在 Mapper接口結(jié)果映射配置如下:

import com.example.demo.model.dto.response.RuleWithLatestVersionDTO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.SelectProvider;
import org.mybatis.dynamic.sql.select.render.SelectStatementProvider;
import org.mybatis.dynamic.sql.util.SqlProviderAdapter;
import org.mybatis.dynamic.sql.util.mybatis3.CommonCountMapper;
import java.util.List;
@Mapper
public interface RuleCustomMapper extends CommonCountMapper {
    // 使用@Result注解處理多表字段
    @SelectProvider(type = SqlProviderAdapter.class, method = "select")
    @Results({
            @Result(column = "id", property = "id"),
            @Result(column = "ruleId", property = "ruleId"),
            @Result(column = "name", property = "name"),
            @Result(column = "domain", property = "domain"),
            @Result(column = "latestVersion", property = "latestVersion"),
            @Result(column = "versionName", property = "versionName"),
            @Result(column = "versionStatus", property = "versionStatus"),
            @Result(column = "versionModifiedDate", property = "versionModifiedDate"),
    })
    List<RuleWithLatestVersionDTO> findByCondition(SelectStatementProvider selectStatement);
}

5. MyBatis Dynamic SQL處理復(fù)雜JOIN和子查詢(xún)

告別繁瑣的 XML 和 OGNL:

  • 痛點(diǎn): 傳統(tǒng)的 MyBatis XML Mapper 文件雖然功能強(qiáng)大,但編寫(xiě)和閱讀動(dòng)態(tài) SQL(使用 <if>, <choose>, <when>, <otherwise>, <foreach> 等標(biāo)簽)在復(fù)雜場(chǎng)景下會(huì)變得冗長(zhǎng)、嵌套深、可讀性下降,且需要掌握 OGNL 表達(dá)式。在 Java 和 XML 之間切換也影響開(kāi)發(fā)效率。
  • 解決: Dynamic SQL 將 SQL 構(gòu)建邏輯完全移回 Java 代碼中,利用 Java 語(yǔ)言的流程控制 (if/else, 循環(huán)) 和強(qiáng)大的 IDE 支持(代碼補(bǔ)全、重構(gòu)、導(dǎo)航),開(kāi)發(fā)體驗(yàn)更流暢、更現(xiàn)代。

比如有如下的一個(gè)sql語(yǔ)句,獲取滿(mǎn)足條件的規(guī)則及其最新版本信息:

SELECT ruleTable.id AS id, ruleTable.rule_id AS ruleId, ruleTable.name AS name, ruleTable.domain AS domain, max_version AS latestVersion
    , ruleVersionTable.name AS versionName, ruleVersionTable.status AS versionStatus, ruleVersionTable.gmt_modified AS versionModifiedDate
FROM rule ruleTable
    JOIN rule_version ruleVersionTable ON ruleTable.rule_id = ruleVersionTable.rule_id
    JOIN (
        SELECT ruleVersionTable.rule_id AS rule_uuid, MAX(ruleVersionTable.version) AS max_version
        FROM rule_version ruleVersionTable
        WHERE ruleVersionTable.id > #{parameters.p1,jdbcType=BIGINT}
        GROUP BY ruleVersionTable.rule_id
    ) max_ver
    ON ruleVersionTable.rule_id = max_ver.rule_uuid
        AND ruleVersionTable.version = max_ver.max_version
WHERE ruleTable.id > #{parameters.p2,jdbcType=BIGINT}
    AND ruleTable.name LIKE #{parameters.p3,jdbcType=VARCHAR}
ORDER BY ruleVersionTable.id
LIMIT #{parameters.p5}, #{parameters.p4}

使用 MyBatis Dynamic SQL 實(shí)現(xiàn)如下【處理復(fù)雜JOIN和子查詢(xún)】:

import com.example.demo.common.model.page.PageRequest;
import com.example.demo.model.query.RuleQueryCondition;
import com.example.demo.repository.generated.RuleEntityDynamicSqlSupport;
import com.example.demo.repository.generated.RuleVersionEntityDynamicSqlSupport;
import org.mybatis.dynamic.sql.SortSpecification;
import org.mybatis.dynamic.sql.SqlColumn;
import org.mybatis.dynamic.sql.SqlTable;
import org.mybatis.dynamic.sql.select.ColumnSortSpecification;
import org.mybatis.dynamic.sql.select.QueryExpressionDSL;
import org.mybatis.dynamic.sql.select.SelectModel;
import org.mybatis.dynamic.sql.select.render.SelectStatementProvider;
import org.mybatis.dynamic.sql.render.RenderingStrategies;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.sql.JDBCType;
import static org.mybatis.dynamic.sql.SqlBuilder.*;
@Component
public class RuleQueryBuilder {
    private final RuleVersionEntityDynamicSqlSupport.RuleVersionEntity ruleVersionDO = RuleVersionEntityDynamicSqlSupport.ruleVersionEntity;
    private final RuleEntityDynamicSqlSupport.RuleEntity ruleDO = RuleEntityDynamicSqlSupport.ruleEntity;
    // 數(shù)據(jù)查詢(xún)
    public SelectStatementProvider buildDataQuery(RuleQueryCondition queryCondition, PageRequest pageRequest) {
        // 1. 創(chuàng)建派生表的別名和列定義
        // 子查詢(xún)的表別名
        String subQueryTable = "max_ver";
        SqlTable maxVerTable = SqlTable.of(subQueryTable);
        SqlColumn<String> maxVerRuleUuid = SqlColumn.of("rule_uuid", maxVerTable, JDBCType.VARCHAR);
        SqlColumn<Integer> maxVerMaxVersion = SqlColumn.of("max_version", maxVerTable, JDBCType.INTEGER);
        // 動(dòng)態(tài)構(gòu)建排序
        List<SortSpecification> sortSpecs = new ArrayList<>();
        SortSpecification sortSpecification = buildSortSpecification(pageRequest.getSort(), pageRequest.getOrder());
        if (sortSpecification != null) {
            sortSpecs.add(sortSpecification);
        }
        // 2.構(gòu)建子查詢(xún)
        QueryExpressionDSL<SelectModel>.GroupByFinisher maxVersionSubQuery = buildMaxVersionSubQuery(queryCondition);
        // 3. 主查詢(xún):關(guān)聯(lián)規(guī)則表、版本表和最大版本子查詢(xún)
        return select(
                ruleDO.id.as("id"),
                ruleDO.ruleId.as("ruleId"),
                ruleDO.name.as("name"),
                ruleDO.domain.as("domain"),
                maxVerMaxVersion.as("latestVersion"),
                ruleVersionDO.name.as("versionName"),
                ruleVersionDO.status.as("versionStatus"),
                ruleVersionDO.gmtModified.as("versionModifiedDate")
        )
                .from(ruleDO, "ruleDO")
                .join(ruleVersionDO, "ruleVersionDO")
                .on(ruleDO.ruleId, equalTo(ruleVersionDO.ruleId))
                .join(maxVersionSubQuery, subQueryTable)
                .on(ruleVersionDO.ruleId, equalTo(maxVerRuleUuid.qualifiedWith(subQueryTable)))
                .and(ruleVersionDO.version, equalTo(maxVerMaxVersion.qualifiedWith(subQueryTable)))
                .where(ruleDO.id, isGreaterThan(0L))
                .and(ruleDO.tenantId, isEqualToWhenPresent(queryCondition.getTenantId()))
                .and(ruleDO.ruleId, isLikeWhenPresent(wrapLike(queryCondition.getRuleId())))
                .and(ruleDO.name, isLikeWhenPresent(wrapLike(queryCondition.getName())))
                .and(ruleDO.creator, isLikeWhenPresent(wrapLike(queryCondition.getCreateBy())))
                .and(ruleDO.type, isEqualToWhenPresent(queryCondition.getType()))
                .and(ruleDO.domain, isEqualToWhenPresent(queryCondition.getDomain()))
                .and(ruleDO.description, isLikeWhenPresent(wrapLike(queryCondition.getDescription())))
                .orderBy(sortSpecs.toArray(new SortSpecification[0]))
                .limit(pageRequest.getPageSize())
                .offset(pageRequest.getOffset())
                .build()
                .render(RenderingStrategies.MYBATIS3);
    }
    // 總數(shù)查詢(xún)
    public SelectStatementProvider buildCountQuery(RuleQueryCondition queryCondition) {
        // 1. 創(chuàng)建派生表的別名和列定義
        String subQueryTable = "max_ver";
        SqlTable maxVerTable = SqlTable.of(subQueryTable);
        SqlColumn<String> maxVerRuleUuid = SqlColumn.of("rule_uuid", maxVerTable, JDBCType.VARCHAR);
        SqlColumn<Integer> maxVerMaxVersion = SqlColumn.of("max_version", maxVerTable, JDBCType.INTEGER);
        // 2. 構(gòu)建子查詢(xún)
        QueryExpressionDSL<SelectModel>.GroupByFinisher maxVersionSubQuery = buildMaxVersionSubQuery(queryCondition);
        // 3. 主查詢(xún):關(guān)聯(lián)規(guī)則表、版本表和最大版本子查詢(xún)
        return select(count())
                .from(ruleDO, "ruleDO")
                .join(ruleVersionDO, "ruleVersionDO")
                .on(ruleDO.ruleId, equalTo(ruleVersionDO.ruleId))
                .join(maxVersionSubQuery, subQueryTable)
                .on(ruleVersionDO.ruleId, equalTo(maxVerRuleUuid.qualifiedWith(subQueryTable)))
                .and(ruleVersionDO.version, equalTo(maxVerMaxVersion.qualifiedWith(subQueryTable)))
                .where(ruleVersionDO.id, isGreaterThan(0L))  // 確保where條件有值
                .and(ruleDO.tenantId, isEqualToWhenPresent(queryCondition.getTenantId()))
                .and(ruleDO.ruleId, isLikeWhenPresent(wrapLike(queryCondition.getRuleId())))
                .and(ruleDO.name, isLikeWhenPresent(wrapLike(queryCondition.getName())))
                .and(ruleDO.creator, isLikeWhenPresent(wrapLike(queryCondition.getCreateBy())))
                .and(ruleDO.type, isEqualToWhenPresent(queryCondition.getType()))
                .and(ruleDO.domain, isEqualToWhenPresent(queryCondition.getDomain()))
                .and(ruleDO.description, isLikeWhenPresent(wrapLike(queryCondition.getDescription())))
                .build()
                .render(RenderingStrategies.MYBATIS3);
    }
    // 公共方法:構(gòu)建最大版本子查詢(xún)
    private QueryExpressionDSL<SelectModel>.GroupByFinisher buildMaxVersionSubQuery(RuleQueryCondition queryCondition) {
        return select(
                ruleVersionDO.ruleId.as("rule_uuid"),
                max(ruleVersionDO.version).as("max_version"))
                .from(ruleVersionDO)
                .where(ruleVersionDO.id, isGreaterThan(0L))
                .and(ruleVersionDO.modifier, isLikeWhenPresent(wrapLike(queryCondition.getUpdateBy())))
                .and(ruleVersionDO.gmtCreate, isGreaterThanOrEqualToWhenPresent(queryCondition.getGmtCreateFrom()))
                .and(ruleVersionDO.gmtCreate, isLessThanOrEqualToWhenPresent(queryCondition.getGmtCreateTo()))
                .and(ruleVersionDO.gmtModified, isGreaterThanOrEqualToWhenPresent(queryCondition.getGmtModifiedFrom()))
                .and(ruleVersionDO.gmtModified, isLessThanOrEqualToWhenPresent(queryCondition.getGmtModifiedTo()))
                .and(ruleVersionDO.description, isLikeWhenPresent(wrapLike(queryCondition.getRuleVersionDesc())))
                .and(ruleVersionDO.name, isLikeWhenPresent(wrapLike(queryCondition.getRuleVersionName())))
                .and(ruleVersionDO.status, isEqualToWhenPresent(queryCondition.getStatus()))
                .groupBy(ruleVersionDO.ruleId);
    }
    private SortSpecification buildSortSpecification(String field, String order) {
        if (field == null) {
            return new ColumnSortSpecification("ruleVersionDO", ruleVersionDO.id);
        }
        ColumnSortSpecification columnSortSpecification;
        switch (field) {
            case "gmtCreate" ->
                    columnSortSpecification = new ColumnSortSpecification("ruleVersionDO", ruleVersionDO.gmtCreate);
            case "gmtModified" ->
                    columnSortSpecification = new ColumnSortSpecification("ruleVersionDO", ruleVersionDO.gmtModified);
            // 其他字段...
            // 默認(rèn)排序邏輯
            default -> columnSortSpecification = new ColumnSortSpecification("ruleVersionDO", ruleVersionDO.id);
        }
        return "asc".equalsIgnoreCase(order) ? columnSortSpecification : columnSortSpecification.descending();
    }
    private String wrapLike(String value) {
        return value != null ? "%" + value + "%" : null;
    }
}

傳統(tǒng) mapper.xml(XML 動(dòng)態(tài) SQL)

<!-- 1. 定義查詢(xún)語(yǔ)句 -->
<select id="selectRulesWithLatestVersion" resultType="RuleWithLatestVersionDTO">
  SELECT 
    ruleTable.id AS id,
    ruleTable.rule_id AS ruleId,
    ruleTable.name AS name,
    ruleTable.domain AS domain,
    max_ver.max_version AS latestVersion,
    ruleVersionTable.name AS versionName,
    ruleVersionTable.status AS versionStatus,
    ruleVersionTable.gmt_modified AS versionModifiedDate
  FROM rule ruleTable
  JOIN rule_version ruleVersionTable 
    ON ruleTable.rule_id = ruleVersionTable.rule_id
  JOIN (
    SELECT 
      rule_id AS rule_uuid, 
      MAX(version) AS max_version
    FROM rule_version
    <where>
      <if test="p1 != null">
        AND id > #{p1}
      </if>
    </where>
    GROUP BY rule_id
  ) max_ver 
    ON ruleVersionTable.rule_id = max_ver.rule_uuid
    AND ruleVersionTable.version = max_ver.max_version
  <where>
    <if test="p2 != null">
      AND ruleTable.id > #{p2}
    </if>
    <if test="p3 != null">
      AND ruleTable.name LIKE CONCAT('%', #{p3}, '%')
    </if>
  </where>
  ORDER BY ruleVersionTable.id
  LIMIT #{p5}, #{p4}
</select>

<!-- 2. Mapper 接口 -->
public interface RuleMapper {
    List<RuleWithLatestVersionDTO> selectRulesWithLatestVersion(
        @Param("p1") Long p1, 
        @Param("p2") Long p2,
        @Param("p3") String namePattern,
        @Param("p4") Integer pageSize,
        @Param("p5") Integer offset);
}

關(guān)鍵差異對(duì)比

特性MyBatis Dynamic SQL傳統(tǒng) mapper.xml
代碼類(lèi)型Java 代碼XML 配置文件
可讀性???? (強(qiáng)類(lèi)型檢查)?? (需切換文件查看)
編譯時(shí)檢查? 類(lèi)型安全? 運(yùn)行時(shí)發(fā)現(xiàn)錯(cuò)誤
動(dòng)態(tài)條件鏈?zhǔn)椒椒ㄕ{(diào)用 (如 .where(...))<if>/<choose> 標(biāo)簽
子查詢(xún)支持通過(guò) DSL 嵌套構(gòu)建原生 SQL 寫(xiě)法
分頁(yè)控制.limit()/.offset() 方法LIMIT 直接拼接
維護(hù)成本中 (需學(xué)習(xí) DSL 語(yǔ)法)低 (SQL 原生寫(xiě)法)
適合場(chǎng)景復(fù)雜動(dòng)態(tài)查詢(xún)、高復(fù)用邏輯簡(jiǎn)單查詢(xún)、團(tuán)隊(duì)熟悉 XML 語(yǔ)法

推薦選擇

  • 新項(xiàng)目推薦 MyBatis Dynamic SQL:類(lèi)型安全 + 更好的重構(gòu)能力
  • 遺留系統(tǒng)或簡(jiǎn)單查詢(xún)可用 mapper.xml:降低學(xué)習(xí)成本

6. Service層

import com.example.demo.common.model.page.PageRequest;
import com.example.demo.common.model.page.PageResult;
import com.example.demo.common.model.page.PaginationUtils;
import com.example.demo.model.dto.response.RuleWithLatestVersionDTO;
import com.example.demo.model.query.RuleQueryCondition;
import com.example.demo.repository.custom.RuleCustomMapper;
import com.example.demo.repository.custom.builder.RuleQueryBuilder;
import com.example.demo.repository.generated.RuleMapper;
import com.example.demo.service.RuleService;
import org.mybatis.dynamic.sql.SqlColumn;
import org.mybatis.dynamic.sql.select.render.SelectStatementProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;
@Service
public class RuleServiceImpl implements RuleService {
    private final RuleCustomMapper ruleCustomMapper;
    private final RuleMapper ruleMapper;
    private final RuleQueryBuilder ruleQueryBuilder;
    @Autowired
    public RuleServiceImpl(RuleMapper ruleMapper, RuleCustomMapper ruleCustomMapper, RuleQueryBuilder ruleQueryBuilder) {
        this.ruleMapper = ruleMapper;
        this.ruleCustomMapper = ruleCustomMapper;
        this.ruleQueryBuilder = ruleQueryBuilder;
    }
    @Override
    public PageResult<RuleWithLatestVersionDTO> findByCondition(RuleQueryCondition condition, PageRequest pageRequest) {
        List<String> columNames = Arrays.stream(ruleMapper.selectList).map(c -> ((SqlColumn<?>) c).name()).toList();
        pageRequest.validateSort(columNames, "排序字段不合法");
        // 構(gòu)建查詢(xún)語(yǔ)句
        SelectStatementProvider selectStatementProvider = ruleQueryBuilder.buildDataQuery(condition, pageRequest);
        System.out.println(selectStatementProvider.getSelectStatement());
        // 構(gòu)建總數(shù)查詢(xún)語(yǔ)句
        SelectStatementProvider countQuery = ruleQueryBuilder.buildCountQuery(condition);
        System.out.println(countQuery.getSelectStatement());
        return PaginationUtils.paginate(pageRequest,
                () -> ruleMapper.count(countQuery),
                () -> ruleCustomMapper.findByCondition(selectStatementProvider));
    }
}

Controller層:

package com.example.demo.controller;
import com.example.demo.common.model.page.PageResult;
import com.example.demo.common.model.response.Result;
import com.example.demo.model.dto.request.RuleQueryPageRequest;
import com.example.demo.model.dto.response.RuleWithLatestVersionDTO;
import com.example.demo.service.RuleService;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api")
@Validated
public class RuleController {
    private final RuleService ruleService;
    @Autowired
    public RuleController(RuleService ruleService) {
        this.ruleService = ruleService;
    }
    @PostMapping("/rule")
    public Result<PageResult<RuleWithLatestVersionDTO>> queryRule(@Valid @RequestBody RuleQueryPageRequest request) {
        PageResult<RuleWithLatestVersionDTO> pageResult = ruleService.findByCondition(request.getQueryCondition(), request.getPageRequest());
        return Result.success(pageResult);
    }
}

總結(jié)

自己封裝分頁(yè)查詢(xún)雖然代碼量稍多,但可控性強(qiáng),避免了PageHelper的線(xiàn)程安全問(wèn)題,適合對(duì)分頁(yè)有定制需求或高并發(fā)場(chǎng)景。

以上就是SpringBoot封裝實(shí)現(xiàn)分頁(yè)查詢(xún)工具的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot分頁(yè)查詢(xún)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 用Java8 stream處理數(shù)據(jù)

    用Java8 stream處理數(shù)據(jù)

    這篇文章主要介紹了用Java8 stream處理數(shù)據(jù),Java 8 API的設(shè)計(jì)者重新提出了一個(gè)新的抽象稱(chēng)為流Stream,可以讓我們以一種聲明的方式處理數(shù)據(jù),此外,數(shù)據(jù)流可以充分利用多核架構(gòu)而無(wú)需編寫(xiě)多線(xiàn)程的一行代碼,下面我們一起來(lái)看看文章詳細(xì)介紹
    2021-11-11
  • IntelliJ IDEA中代碼一鍵生成方法

    IntelliJ IDEA中代碼一鍵生成方法

    EasyCode 是基于 IntelliJ IDEA 開(kāi)發(fā)的代碼生成插件,支持自定義任意模板(Java,html,js,xml),這篇文章主要介紹了IntelliJ IDEA中代碼一鍵生成方法,需要的朋友可以參考下
    2020-02-02
  • Linux配置jdk1.8與jdk17兼容并存并啟動(dòng)jar包指定jdk版本

    Linux配置jdk1.8與jdk17兼容并存并啟動(dòng)jar包指定jdk版本

    JDK是Java語(yǔ)言的軟件開(kāi)發(fā)工具包,主要用于移動(dòng)設(shè)備、嵌入式設(shè)備上的java應(yīng)用程序,這篇文章主要給大家介紹了關(guān)于Linux配置jdk1.8與jdk17兼容并存并啟動(dòng)jar包指定jdk版本的相關(guān)資料,需要的朋友可以參考下
    2024-08-08
  • SpringBoot2.2.X用Freemarker出現(xiàn)404的解決

    SpringBoot2.2.X用Freemarker出現(xiàn)404的解決

    這篇文章主要介紹了SpringBoot2.2.X用Freemarker出現(xiàn)404的解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-02-02
  • ConcurrentModificationException日志關(guān)鍵字報(bào)警思考分析

    ConcurrentModificationException日志關(guān)鍵字報(bào)警思考分析

    本文將記錄和分析日志中的ConcurrentModificationException關(guān)鍵字報(bào)警,還有一些我的思考,有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2023-12-12
  • 詳解如何使用MyBatis簡(jiǎn)化JDBC開(kāi)發(fā)

    詳解如何使用MyBatis簡(jiǎn)化JDBC開(kāi)發(fā)

    JavaEE?企業(yè)級(jí)?Java?項(xiàng)目中的經(jīng)典三層架構(gòu)為表現(xiàn)層,業(yè)務(wù)層和持久層.MyBatis?對(duì)?JDBC?代碼進(jìn)行了封裝,作為一款優(yōu)秀的持久層框架,專(zhuān)門(mén)用于簡(jiǎn)化JDBC開(kāi)發(fā).本文主要介紹一下如何使用MyBatis簡(jiǎn)化JDBC開(kāi)發(fā),需要的可以參考一下
    2023-01-01
  • java中的內(nèi)存溢出方式

    java中的內(nèi)存溢出方式

    文章介紹了如何使用jmap和IBMHeapAnalyzer等工具分析OutOfMemoryError: Compressedclassspace錯(cuò)誤,發(fā)現(xiàn)問(wèn)題出在/org/pf4j/PluginClassLoader加載了大量類(lèi)
    2024-12-12
  • SpringBoot如何將applicaton.properties中的數(shù)據(jù)綁定到bean中

    SpringBoot如何將applicaton.properties中的數(shù)據(jù)綁定到bean中

    SpringBoot中我們?cè)撊绾螌pplicaton.properties中的數(shù)據(jù)綁定到bean中呢?在之間我們是可以通過(guò)@value注解,在SpringBoot自動(dòng)啟動(dòng)后,會(huì)自動(dòng)的去相應(yīng)的路徑中去尋找applicaton.properties配置文件,然后將相應(yīng)的值賦值上,感興趣的朋友一起看看吧
    2025-05-05
  • 普通對(duì)象使用spring容器中的對(duì)象的實(shí)現(xiàn)方法

    普通對(duì)象使用spring容器中的對(duì)象的實(shí)現(xiàn)方法

    這篇文章主要介紹了普通對(duì)象使用spring容器中的對(duì)象的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-06-06
  • java并發(fā)包中CountDownLatch和線(xiàn)程池的使用詳解

    java并發(fā)包中CountDownLatch和線(xiàn)程池的使用詳解

    這篇文章主要介紹了java并發(fā)包中CountDownLatch和線(xiàn)程池的使用詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-02-02

最新評(píng)論