当前位置: 代码网 > it编程>编程语言>Java > SpringBoot封装实现分页查询工具

SpringBoot封装实现分页查询工具

2025年06月20日 Java 我要评论
概述在spring boot项目中使用mybatis进行分页查询,通常有两种主流方式:使用mybatis内置的rowbounds进行内存分页(不推荐,数据量大时性能差)使用分页插件,如pagehelp

概述

在spring boot项目中使用mybatis进行分页查询,通常有两种主流方式:

  • 使用mybatis内置的rowbounds进行内存分页(不推荐,数据量大时性能差)
  • 使用分页插件,如pagehelper

使用pagehelper可能遇到的一些问题

pagehelper 是一个非常流行的 mybatis 分页插件,但它也有一些潜在的缺点和需要注意的地方。以下是在使用 pagehelper 时可能遇到的一些问题:

1.线程安全问题

问题描述pagehelper.startpage() 方法使用了 threadlocal 来保存分页参数。如果在同一个线程中多次调用 startpage() 而没有及时清理(比如在 finally 块中调用 pagehelper.clearpage()),或者线程被复用(如线程池场景),可能导致分页参数混乱。

2.对复杂 sql 的支持有限

问题描述:pagehelper 通过拦截 sql 并重写来实现分页。对于特别复杂的 sql(例如包含多个嵌套子查询、union 等),自动生成的 count 查询语句可能会出错,导致分页结果不正确。

3.性能问题

count 查询效率:默认情况下,pagehelper 会执行一个 count 查询获取总记录数。如果表数据量非常大(上千万),这个 count 操作可能很慢(尤其是没有合适索引时)。

4.与其它拦截器冲突

问题描述:如果项目中同时使用了多个 mybatis 拦截器(如数据权限拦截器、加解密拦截器等),拦截器的执行顺序可能影响 pagehelper 的正常工作(因为分页依赖于改写 sql)。确保 pagehelper 是最后一个执行的拦截器(可以通过调整拦截器添加顺序实现)。

5.对异步/多线程支持不友好

问题描述:由于依赖 threadlocal,如果在异步任务(如 @async)或多线程环境中使用 pagehelper,分页参数可能无法正确传递到子线程。

6.返回对象过于臃肿

问题描述pageinfo 对象包含大量分页信息(如总页数、导航页码列表等),但实际业务中可能只需要部分字段(如当前页数据、总记录数)。

7.设计耦合

问题描述:分页逻辑侵入业务代码(service 层中显式调用 pagehelper.startpage()),违反了分层设计的纯粹性。

建议:pagehelper 适合中小型项目的快速开发,但在高并发、大数据量、复杂sql场景下需谨慎使用,必要时采用更可控的分页方案。

自定义分页查询工具

我们可以在spring boot项目中不使用pagehelper,而是自己封装一个分页查询工具。主要思路如下:

  • 定义一个分页请求参数类,包含页码和每页数量。
  • 定义一个分页结果类,包含数据列表、总记录数、总页数、当前页码、每页数量等信息。
  • 定义一个分页查询工具:先查询总数,再查询当前页数据,然后封装成分页结果对象。
  • 使用mybatis dynamic sql自定义复杂分页查询逻辑:一个用于查询符合条件的总记录数,一个用于查询当前页的数据(使用数据库的分页语法,如mysql的limit)。

下面我们一步步实现:

步骤1:创建分页请求参数类(pagerequest)

步骤2:创建分页结果类(pageresult)

步骤3:创建分页查询工具(paginationutils)

步骤4:在mapper接口中使用mybatis dynamic sql自定义复杂分页查询逻辑

步骤5:在service层调用mapper的两个方法,并封装pageresult

步骤6:在controller中接收分页参数,调用service方法

分页查询具体代码实现

1. 分页请求参数类(pagerequest)

import jakarta.validation.constraints.max;
import jakarta.validation.constraints.min;
import jakarta.validation.constraints.pattern;
import java.util.list;
import java.util.set;
/**
 * 分页请求参数封装类
 */
public class pagerequest {
    /**
     * 默认第一页
     */
    public static final int default_page_num = 1;
    /**
     * 默认每页10条
     */
    public static final int default_page_size = 10;
    /**
     * 默认排序方向 - 升序
     */
    public static final string default_order = "desc";
    /**
     * 最大允许的每页记录数
     */
    public static final int max_page_size = 1000;
    /**
     * 当前页码(从1开始)
     */
    @min(value = 1, message = "页码不能小于1")
    private int pagenum = default_page_num;
    /**
     * 每页记录数
     */
    @min(value = 1, message = "每页数量不能小于1")
    @max(value = max_page_size, message = "每页数量不能超过" + 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;
    // 无参构造器
    public pagerequest() {
    }
    /**
     * 带页码和每页数量的构造器
     *
     * @param pagenum  当前页码
     * @param pagesize 每页数量
     */
    public pagerequest(int pagenum, int pagesize) {
        this.pagenum = pagenum;
        this.pagesize = pagesize;
    }
    /**
     * 带所有参数的构造器
     *
     * @param pagenum  当前页码
     * @param pagesize 每页数量
     * @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;
    }
    /**
     * 计算偏移量(用于数据库分页查询)
     *
     * @return 当前页的起始位置
     */
    public int getoffset() {
        return (pagenum - 1) * pagesize;
    }
    /**
     * 验证排序字段是否在允许的列表中
     *
     * @param allowedfields 允许的排序字段集合
     * @return 如果排序字段有效返回true,否则返回false
     */
    public boolean issortvalid(set<string> allowedfields) {
        if (sort == null || sort.isempty()) {
            return true;
        }
        return allowedfields.contains(sort);
    }
    /**
     * 验证排序字段是否在允许的列表中,无效时抛出异常
     *
     * @param allowedfields 允许的排序字段集合
     * @param errormessage  错误信息
     * @throws illegalargumentexception 如果排序字段无效
     */
    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. 分页结果类(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;        // 当前页码
    private final int pagesize;        // 每页数量
    private final long total;      // 总记录数
    private final int totalpage;       // 总页数
    private final list<t> data;    // 当前页数据
    private final string sort;     // 排序字段
    private final string order;    // 排序方向
    /**
     * 构造函数
     *
     * @param pagerequest 分页请求
     * @param total       总记录数
     * @param data        当前页数据
     */
    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;
    }
    /**
     * 构造函数
     *
     * @param pagenum  当前页码
     * @param pagesize  每页数量
     * @param total 总记录数
     * @param data  当前页数据
     * @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;
        // 计算总页数
        this.totalpage = calculatetotalpage(total, pagesize);
    }
    /**
     * 计算总页数
     *
     * @param total 总记录数
     * @param size  每页数量
     * @return 总页数
     */
    private int calculatetotalpage(long total, int size) {
        if (size <= 0) return 0;
        return (int) math.ceil((double) total / size);
    }
    // ================ 实用静态方法 ================ //
    /**
     * 创建空的分页结果
     *
     * @param <t> 数据类型
     * @return 空的分页结果
     */
    public static <t> pageresult<t> empty() {
        return new pageresult<>(1, 0, 0, collections.emptylist(), null, null);
    }
    /**
     * 基于 pagerequest 创建空的分页结果
     *
     * @param pagerequest 分页请求
     * @param <t>         数据类型
     * @return 空的分页结果
     */
    public static <t> pageresult<t> empty(pagerequest pagerequest) {
        return new pageresult<>(
                pagerequest.getpagenum(),
                pagerequest.getpagesize(),
                0,
                collections.emptylist(),
                pagerequest.getsort(),
                pagerequest.getorder()
        );
    }
    /**
     * 创建单页结果(适用于数据量小的情况)
     *
     * @param data 所有数据
     * @param <t>  数据类型
     * @return 单页结果
     */
    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);
    }
    /**
     * 创建分页结果(基于 pagerequest)
     *
     * @param pagerequest 分页请求
     * @param total       总记录数
     * @param data        当前页数据
     * @param <t>         数据类型
     * @return 分页结果
     */
    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()
        );
    }
    /**
     * 转换分页结果的数据类型
     *
     * @param source 源分页结果
     * @param mapper 数据转换函数
     * @param <t>    源数据类型
     * @param <r>    目标数据类型
     * @return 转换后的分页结果
     */
    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()
        );
    }
    /**
     * 合并两个分页结果(适用于并行查询场景)
     *
     * @param result1  第一个分页结果
     * @param result2  第二个分页结果
     * @param combiner 数据合并函数
     * @param <t>      第一个结果的数据类型
     * @param <u>      第二个结果的数据类型
     * @param <r>      合并后的数据类型
     * @return 合并后的分页结果
     */
    public static <t, u, r> pageresult<r> combine(
            pageresult<t> result1,
            pageresult<u> result2,
            bifunction<t, u, r> combiner) {
        // 验证分页信息是否一致
        if (result1.getpagenum() != result2.getpagenum() ||
                result1.getpagesize() != result2.getpagesize() ||
                result1.gettotal() != result2.gettotal()) {
            throw new illegalargumentexception("page results are not compatible for combination");
        }
        // 验证数据数量是否一致
        if (result1.getdata().size() != result2.getdata().size()) {
            throw new illegalargumentexception("data lists have different sizes");
        }
        // 合并数据
        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. 创建分页查询工具(paginationutils)

import java.util.list;
import java.util.function.supplier;
public class paginationutils {
    /**
     * 执行分页查询(使用pagerequest对象)
     *
     * @param pagerequest   分页请求(包含页码、大小、排序等信息)
     * @param countfunction 查询总数的函数
     * @param datafunction  查询数据的函数
     * @return 分页结果
     */
    public static <t> pageresult<t> paginate(pagerequest pagerequest,
                                             supplier<long> countfunction,
                                             supplier<list<t>> datafunction) {
        // 查询总数
        long total = countfunction.get();
        // 如果没有数据,直接返回空结果
        if (total == 0) {
            return pageresult.empty(pagerequest);
        }
        // 查询当前页数据
        list<t> data = datafunction.get();
        return new pageresult<>(pagerequest, total, data);
    }
}

4. mapper接口示例(使用mybatis dynamic sql)

当进行join或复杂子查询时,查询结果通常涉及多个实体,因此需要自定义结果映射。mybatis dynamic sql本身不处理结果映射,你需要:

  • 使用注解:在mapper接口的方法上使用@results@result注解定义映射关系。
  • 使用xml:在mapper xml文件中定义<resultmap>

例如,对于规则和规则版本(一对多)的join查询,结果封装到一个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接口结果映射配置如下:

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处理复杂join和子查询

告别繁琐的 xml 和 ognl:

  • 痛点: 传统的 mybatis xml mapper 文件虽然功能强大,但编写和阅读动态 sql(使用 <if>, <choose>, <when>, <otherwise>, <foreach> 等标签)在复杂场景下会变得冗长、嵌套深、可读性下降,且需要掌握 ognl 表达式。在 java 和 xml 之间切换也影响开发效率。
  • 解决: dynamic sql 将 sql 构建逻辑完全移回 java 代码中,利用 java 语言的流程控制 (if/else, 循环) 和强大的 ide 支持(代码补全、重构、导航),开发体验更流畅、更现代。

比如有如下的一个sql语句,获取满足条件的规则及其最新版本信息:

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 实现如下【处理复杂join和子查询】:

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;
    // 数据查询
    public selectstatementprovider builddataquery(rulequerycondition querycondition, pagerequest pagerequest) {
        // 1. 创建派生表的别名和列定义
        // 子查询的表别名
        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);
        // 动态构建排序
        list<sortspecification> sortspecs = new arraylist<>();
        sortspecification sortspecification = buildsortspecification(pagerequest.getsort(), pagerequest.getorder());
        if (sortspecification != null) {
            sortspecs.add(sortspecification);
        }
        // 2.构建子查询
        queryexpressiondsl<selectmodel>.groupbyfinisher maxversionsubquery = buildmaxversionsubquery(querycondition);
        // 3. 主查询:关联规则表、版本表和最大版本子查询
        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);
    }
    // 总数查询
    public selectstatementprovider buildcountquery(rulequerycondition querycondition) {
        // 1. 创建派生表的别名和列定义
        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. 构建子查询
        queryexpressiondsl<selectmodel>.groupbyfinisher maxversionsubquery = buildmaxversionsubquery(querycondition);
        // 3. 主查询:关联规则表、版本表和最大版本子查询
        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);
    }
    // 公共方法:构建最大版本子查询
    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);
            // 其他字段...
            // 默认排序逻辑
            default -> columnsortspecification = new columnsortspecification("ruleversiondo", ruleversiondo.id);
        }
        return "asc".equalsignorecase(order) ? columnsortspecification : columnsortspecification.descending();
    }
    private string wraplike(string value) {
        return value != null ? "%" + value + "%" : null;
    }
}

传统 mapper.xml(xml 动态 sql)

<!-- 1. 定义查询语句 -->
<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);
}

关键差异对比

特性mybatis dynamic sql传统 mapper.xml
代码类型java 代码xml 配置文件
可读性⭐⭐⭐⭐ (强类型检查)⭐⭐ (需切换文件查看)
编译时检查✅ 类型安全❌ 运行时发现错误
动态条件链式方法调用 (如 .where(...))<if>/<choose> 标签
子查询支持通过 dsl 嵌套构建原生 sql 写法
分页控制.limit()/.offset() 方法limit 直接拼接
维护成本中 (需学习 dsl 语法)低 (sql 原生写法)
适合场景复杂动态查询、高复用逻辑简单查询、团队熟悉 xml 语法

推荐选择

  • 新项目推荐 mybatis dynamic sql:类型安全 + 更好的重构能力
  • 遗留系统或简单查询可用 mapper.xml:降低学习成本

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, "排序字段不合法");
        // 构建查询语句
        selectstatementprovider selectstatementprovider = rulequerybuilder.builddataquery(condition, pagerequest);
        system.out.println(selectstatementprovider.getselectstatement());
        // 构建总数查询语句
        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);
    }
}

总结

自己封装分页查询虽然代码量稍多,但可控性强,避免了pagehelper的线程安全问题,适合对分页有定制需求或高并发场景。

以上就是springboot封装实现分页查询工具的详细内容,更多关于springboot分页查询的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com