Mybatis3详解(十四)----Mybatis的分页
1、前言
在前面学习mybatis的时候,会经常对数据进行增删改查操作,使用最多的是对数据库进行查询操作,但是前面都是简单的案例,所以查询的数据量不是很大,自然查询时没有任何压力,但是如果在实际的项目中,数据库的数据成千上万,如果还是这样一次性查询出所有数据,那么会导致数据可读性和数据库性能极差。所以我们往往使用分页进行查询,这样对数据库压力就在可控范围内。
这里介绍Mybatis的这几种分页方式:
- 原生SQL的Limit分页
- Mybatis自带的RowBounds分页
- 自定义拦截器插件进行分页
- 使用PageHelper插件分页
下面我们来简单学习一下。
2、原生Limit分页
原生Limit分页就是在编写sql语句时需要自己加上limit关键字,然后传入分页参数进行分页,例如select * from t_user limit 0,3;
①、编写UserMapper接口
01 02 03 04 05 06 07 | /** * UserMapper接口 */ public interface UserMapper { //分页查询所有用户,通过原生limit List<User> selectAllUserByLimit(Map map); } |
②、UserMapper.xml映射文件
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 | <? xml version="1.0" encoding="UTF-8"?> <! DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> < mapper namespace="com.thr.mapper.UserMapper"> < resultMap id="userMap" type="com.thr.pojo.User"> < id property="userId" column="id"/> < result property="userName" column="username"/> < result property="userAge" column="age"/> < result property="userBirthday" column="birthday"/> < result property="userSex" column="sex"/> < result property="userAddress" column="address"/> </ resultMap > <!-- 分页查询所有用户,通过原生limit --> < select id="selectAllUserByLimit" resultMap="userMap"> select * from t_user limit #{start},#{size} </ select > </ mapper > |
③、测试分页方法
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | //Mybatis的测试 public class MybatisTest2 { //定义 SqlSession private SqlSession sqlSession = null ; //定义 UserMapper对象 private UserMapper mapper = null ; @Before //在测试方法执行之前执行 public void getSqlSession(){ //1、加载 mybatis 全局配置文件 InputStream is = MybatisTest2. class .getClassLoader().getResourceAsStream( "mybatis-config.xml" ); //2、创建SqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); //3、根据 sqlSessionFactory 产生session sqlSession = sqlSessionFactory.openSession(); //4、创建Mapper接口的的代理对象,getMapper方法底层会通过动态代理生成UserMapper的代理实现类 mapper = sqlSession.getMapper(UserMapper. class ); } @After //在测试方法执行完成之后执行 public void destroy() throws IOException { sqlSession.commit(); sqlSession.close(); } //分页查询所有用户信息,通过原生limit @Test public void selectAllUserByLimit(){ int currPage = 2 ; //当前页码 int pageSize = 3 ; //当前显示页记录数量 HashMap<String, Object> map = new HashMap<>(); //计算起始位置,注意:currPage和start别搞错了,一个表示当前页码,一个是从第几行读取记录 map.put( "start" ,(currPage- 1 )*pageSize); //页面显示记录数 map.put( "size" ,pageSize); System.out.println( "当前页码为:第" +currPage+ "页,页面显示记录数量:" +pageSize+ "个" ); List<User> userList = mapper.selectAllUserByLimit(map); for (User user : userList) { System.out.println(user); } } } |
④、运行结果
3、RowBounds分页
Mybatis内置了一个专门处理分页的类——RowBounds,我们使用它可以轻松完成分页。
RowBounds源代码如下:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | package org.apache.ibatis.session; public class RowBounds { //默认值为0~~Java最大整数 public static final int NO_ROW_OFFSET = 0 ; public static final int NO_ROW_LIMIT = Integer.MAX_VALUE; public static final RowBounds DEFAULT = new RowBounds(); //偏移量,即从第几行开始读取 private final int offset; //限制,即每页显示记录数量 private final int limit; public RowBounds() { this .offset = NO_ROW_OFFSET; this .limit = NO_ROW_LIMIT; } public RowBounds( int offset, int limit) { this .offset = offset; this .limit = limit; } public int getOffset() { return offset; } public int getLimit() { return limit; } } |
那么我们怎样来使用这个RowBounds分页呢?非常的简单。
①、定义接口方法
01 02 | //分页查询所有用户,通过自带的RowBounds List<User> selectAllUserByRowBounds(RowBounds rowBounds); |
②、sql映射
01 02 03 04 | <!-- 分页查询所有用户,通过自带的RowBounds --> < select id="selectAllUserByRowBounds" resultMap="userMap"> select * from t_user </ select > |
使用RowBounds分页我们可以不写在映射SQL中写limit关键字,到时候自动回给我们拼接。就两个字,方便!
③、测试方法
01 02 03 04 05 06 07 08 09 10 11 12 13 | //分页查询所有用户信息,通过自带的RowBounds @Test public void selectAllUserByRowBounds(){ int currPage= 2 ; //当前页码 int pageSize= 3 ; //当前页显示记录数量 //注意:currPage和start别搞错了,一个表示当前页码,一个是从第几行读取记录 int start = (currPage- 1 )*pageSize; //计算从第几行读取记录 RowBounds rowBounds = new RowBounds(start,pageSize); List<User> userList = mapper.selectAllUserByRowBounds(rowBounds); for (User user : userList) { System.out.println(user); } } |
④、运行结果
RowBounds分页有一点好处就是处理数据量少时还可以,但是数据量大时,就不行好用了,此时一般都会实现拦截器来完成分页。
4、自定义拦截器插件分页
自定义拦截器插件分页 需要自己定义一个类实现Interceptor接口,这个接口是Mybatis提供的。任何分页插件想要对Mybatis进行分页就必须实现Interceptor接口,包括后面PageHelper分页插件。
①、创建MyPageInterceptor类
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | /** * @Intercepts 表示是一个拦截器 * @Signature 拦截器的签名 * type 拦截的类型 四大对象之一( Executor,ResultSetHandler,ParameterHandler,StatementHandler) * method 拦截的方法 */ @Intercepts ({ @Signature (type=StatementHandler. class ,method= "prepare" ,args={Connection. class , Integer. class })}) public class MyPageInterceptor implements Interceptor { //当前页码 private int currPage; //每页显示的条目数 private int pageSize; //数据库类型 private String dbType; @Override public Object intercept(Invocation invocation) throws Throwable { System.out.println( "plugin is running..." ); //获取StatementHandler,默认是RoutingStatementHandler StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); //获取statementHandler包装类 MetaObject MetaObjectHandler = SystemMetaObject.forObject(statementHandler); //分离代理对象链 while (MetaObjectHandler.hasGetter( "h" )) { Object obj = MetaObjectHandler.getValue( "h" ); MetaObjectHandler = SystemMetaObject.forObject(obj); } while (MetaObjectHandler.hasGetter( "target" )) { Object obj = MetaObjectHandler.getValue( "target" ); MetaObjectHandler = SystemMetaObject.forObject(obj); } //获取连接对象 //Connection connection = (Connection) invocation.getArgs()[0]; //object.getValue("delegate"); 获取StatementHandler的实现类 //获取查询接口映射的相关信息 MappedStatement mappedStatement = (MappedStatement) MetaObjectHandler.getValue( "delegate.mappedStatement" ); String mapId = mappedStatement.getId(); //statementHandler.getBoundSql().getParameterObject(); //拦截以.ByPage结尾的请求,分页功能的统一实现 if (mapId.matches( ".+ByPage$" )) { //获取进行数据库操作时管理参数的handler ParameterHandler parameterHandler = (ParameterHandler) MetaObjectHandler.getValue( "delegate.parameterHandler" ); //获取请求时的参数 Map<String, Object> paraObject = (Map<String, Object>) parameterHandler.getParameterObject(); //也可以这样获取 //paraObject = (Map<String, Object>) statementHandler.getBoundSql().getParameterObject(); //参数名称和在service中设置到map中的名称一致 currPage = ( int ) paraObject.get( "currPage" ); pageSize = ( int ) paraObject.get( "pageSize" ); String sql = (String) MetaObjectHandler.getValue( "delegate.boundSql.sql" ); //也可以通过statementHandler直接获取 //sql = statementHandler.getBoundSql().getSql(); //构建分页功能的sql语句 String limitSql; sql = sql.trim(); limitSql = sql + " limit " + (currPage - 1 ) * pageSize + "," + pageSize; //将构建完成的分页sql语句赋值个体'delegate.boundSql.sql',偷天换日 MetaObjectHandler.setValue( "delegate.boundSql.sql" , limitSql); } //调用原对象的方法,进入责任链的下一级 return invocation.proceed(); } //获取代理对象 @Override public Object plugin(Object o) { //生成object对象的动态代理对象 return Plugin.wrap(o, this ); } //设置代理对象的参数 @Override public void setProperties(Properties properties) { //如果项目中分页的pageSize是统一的,也可以在这里统一配置和获取,这样就不用每次请求都传递pageSize参数了。参数是在配置拦截器时配置的。 String limit1 = properties.getProperty( "limit" , "10" ); this .pageSize = Integer.valueOf(limit1); this .dbType = properties.getProperty( "dbType" , "mysql" ); } } |
②、全局配置文件增加plugin设置(注意位置)
01 02 03 04 05 | <!-- 配置自定义分页插件 --> < plugins > < plugin interceptor="com.thr.interceptor.MyPageInterceptor"> </ plugin > </ plugins > |
③、接口方法
01 02 | //分页查询所有用户,通过原生自定义拦截器 List<User> selectAllUserByPage(Map map); |
由于拦截器中设置了拦截以.ByPage结尾的方法,所以方法一定要命名正确,
④、sql映射
01 02 03 04 | <!-- 分页查询所有用户,通过自定义拦截器 --> < select id="selectAllUserByPage" resultMap="userMap"> select * from t_user </ select > |
⑤、测试方法
5、PageHelper分页插件
PageHelper是一款非常优秀的分页插件,用的人非常多,详细的可以参考PageHelper的官方文档,讲的比较通俗易懂。链接:https://pagehelper.github.io/docs/howtouse/。 PageHelper分页其实也是自定义拦截器方式的一种第三方实现,它内部帮助我们实现了Interceptor的功能。所以实际上我们在执行查询方法之前,PageHelper分页插件同样是对我们的 sql 进行拦截,然后对分页参数进行拼接。
PageHelper的简单使用:
①、引入PageHelper依赖:
01 02 03 04 05 06 | <!-- pagehelper分页插件 --> < dependency > < groupId >com.github.pagehelper</ groupId > < artifactId >pagehelper</ artifactId > < version >5.2.0</ version > </ dependency > |
②、全局配置文件增加plugin设置(注意位置)
01 02 03 04 05 | <!-- 配置分页插件 --> < plugins > <!-- PageHelper5版本配置 --> < plugin interceptor="com.github.pagehelper.PageInterceptor"/> </ plugins > |
③、接口方法
01 02 | //分页查询所有用户,通过PageHelper List<User> selectAllUserByPageHelper(); |
④、sql映射
01 02 03 04 | <!-- 分页查询所有用户,通过PageHelper --> < select id="selectAllUserByPageHelper" resultMap="userMap"> select * from t_user </ select > |
⑤、测试方法
01 02 03 04 05 06 07 08 09 10 11 12 | //分页查询所有用户信息,通过PageHelper @Test public void selectAllUserByPageHelper(){ int currPage = 2 ; //当前页码 int pageSize = 3 ; //当前页记录数量 //表示获取第2页,3条内容,默认会查询总数count PageHelper.startPage(currPage,pageSize); List<User> userList = mapper.selectAllUserByPageHelper(); for (User user : userList) { System.out.println(user); } } |
⑥、运行结果
以上只是PageHelper的简单介绍,还有更多的功能可以去参考官方文档,也可以自行百度学习。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南