概述
在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分页查询的资料请关注代码网其它相关文章!
发表评论