1、前言
在前面学习mybatis的时候,会经常对数据进行增删改查操作,使用最多的是对数据库进行查询操作,但是前面都是简单的案例,所以查询的数据量不是很大,自然查询时没有任何压力,但是如果在实际的项目中,数据库的数据成千上万,如果还是这样一次性查询出所有数据,那么会导致数据可读性和数据库性能极差。所以我们往往使用分页进行查询,这样对数据库压力就在可控范围内。
这里介绍Mybatis的这几种分页方式:
- 原生SQL的Limit分页
- Mybatis自带的RowBounds分页
- 自定义拦截器插件进行分页
- 使用PageHelper插件分页
下面我们来简单学习一下。
2、原生Limit分页
原生Limit分页就是在编写sql语句时需要自己加上limit关键字,然后传入分页参数进行分页,例如select * from t_user limit 0,3;
①、编写UserMapper接口
/** * UserMapper接口 */ public interface UserMapper { //分页查询所有用户,通过原生limit List<User> selectAllUserByLimit(Map map); }
②、UserMapper.xml映射文件
<?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>
③、测试分页方法
//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源代码如下:
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分页呢?非常的简单。
①、定义接口方法
//分页查询所有用户,通过自带的RowBounds List<User> selectAllUserByRowBounds(RowBounds rowBounds);
②、sql映射
<!-- 分页查询所有用户,通过自带的RowBounds --> <select id="selectAllUserByRowBounds" resultMap="userMap"> select * from t_user </select>
使用RowBounds分页我们可以不写在映射SQL中写limit关键字,到时候自动回给我们拼接。就两个字,方便!
③、测试方法
//分页查询所有用户信息,通过自带的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类
/** * @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设置(注意位置)
<!-- 配置自定义分页插件 --> <plugins> <plugin interceptor="com.thr.interceptor.MyPageInterceptor"> </plugin> </plugins>
③、接口方法
//分页查询所有用户,通过原生自定义拦截器 List<User> selectAllUserByPage(Map map);
由于拦截器中设置了拦截以.ByPage结尾的方法,所以方法一定要命名正确,
④、sql映射
<!-- 分页查询所有用户,通过自定义拦截器 --> <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依赖:
<!-- pagehelper分页插件 --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.2.0</version> </dependency>
②、全局配置文件增加plugin设置(注意位置)
<!-- 配置分页插件 --> <plugins> <!-- PageHelper5版本配置 --> <plugin interceptor="com.github.pagehelper.PageInterceptor"/> </plugins>
③、接口方法
//分页查询所有用户,通过PageHelper List<User> selectAllUserByPageHelper();
④、sql映射
<!-- 分页查询所有用户,通过PageHelper --> <select id="selectAllUserByPageHelper" resultMap="userMap"> select * from t_user </select>
⑤、测试方法
//分页查询所有用户信息,通过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的简单介绍,还有更多的功能可以去参考官方文档,也可以自行百度学习。