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