• Mybatis3详解(十四)——Mybatis的分页


    1、前言

           在前面学习mybatis的时候,会经常对数据进行增删改查操作,使用最多的是对数据库进行查询操作,但是前面都是简单的案例,所以查询的数据量不是很大,自然查询时没有任何压力,但是如果在实际的项目中,数据库的数据成千上万,如果还是这样一次性查询出所有数据,那么会导致数据可读性和数据库性能极差。所以我们往往使用分页进行查询,这样对数据库压力就在可控范围内。

           这里介绍Mybatis的这几种分页方式:

    1. 原生SQL的Limit分页
    2. Mybatis自带的RowBounds分页
    3. 自定义拦截器插件进行分页
    4. 使用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);
            }
        }
    }

           ④、运行结果

    image

    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);
            }
        }

           ④、运行结果

    image

           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>

           ⑤、测试方法

    image

    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);
            }
        }

           ⑥、运行结果

    image

           以上只是PageHelper的简单介绍,还有更多的功能可以去参考官方文档,也可以自行百度学习。

  • 相关阅读:
    【AtCoder】AtCoder Grand Contest 017 解题报告
    【洛谷2523】[HAOI2011] Problem c(DP水题)
    【洛谷1912】[NOI2009] 诗人小G(决策单调性优化DP)
    【AtCoder】AtCoder Grand Contest 018 解题报告
    【洛谷3237】[HNOI2014] 米特运输(哈希)
    【洛谷2217】[HAOI2007] 分割矩阵(DP水题)
    【洛谷1039】侦探推理(字符串模拟题)
    【AtCoder】AtCoder Grand Contest 019 解题报告
    【洛谷4965】薇尔莉特的打字机(假装有棵Trie树)
    【CF512D】Fox And Travelling(拓扑+树上背包)
  • 原文地址:https://www.cnblogs.com/tanghaorong/p/14017180.html
Copyright © 2020-2023  润新知