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的簡單介紹,還有更多的功能可以去參考官方文檔,也可以自行百度學習。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM