MyBatis 物理分頁


MyBatis使用RowBounds實現的分頁是邏輯分頁,也就是先把數據記錄全部查詢出來,然在再根據offset和limit截斷記錄返回

為了在數據庫層面上實現物理分頁,又不改變原來MyBatis的函數邏輯,可以編寫plugin截獲MyBatis Executor的statementhandler,重寫SQL來執行查詢

參考資料: http://blog.csdn.net/hupanfeng/article/details/9265341

下面的插件代碼只針對MySQL

plugin代碼

package plugin;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.RowBounds;

/**
 * 通過攔截<code>StatementHandler</code>的<code>prepare</code>方法,重寫sql語句實現物理分頁。
 * 老規矩,簽名里要攔截的類型只能是接口。
 *
 */
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class})})
public class PaginationInterceptor implements Interceptor {
    private static final Log logger = LogFactory.getLog(PaginationInterceptor.class);
    private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
    private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
    private static String DEFAULT_PAGE_SQL_ID = ".*Page$"; // 需要攔截的ID(正則匹配)

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, DEFAULT_OBJECT_FACTORY,
                DEFAULT_OBJECT_WRAPPER_FACTORY);
        RowBounds rowBounds = (RowBounds) metaStatementHandler.getValue("delegate.rowBounds");
        // 分離代理對象鏈(由於目標類可能被多個攔截器攔截,從而形成多次代理,通過下面的兩次循環可以分離出最原始的的目標類)
        while (metaStatementHandler.hasGetter("h")) {
            Object object = metaStatementHandler.getValue("h");
            metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);
        }
        // 分離最后一個代理對象的目標類
        while (metaStatementHandler.hasGetter("target")) {
            Object object = metaStatementHandler.getValue("target");
            metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);
        }

        // property在mybatis settings文件內配置
        Configuration configuration = (Configuration) metaStatementHandler.getValue("delegate.configuration");

        // 設置pageSqlId
        String pageSqlId = configuration.getVariables().getProperty("pageSqlId");
        if (null == pageSqlId || "".equals(pageSqlId)) {
            logger.warn("Property pageSqlId is not setted,use default '.*Page$' ");
            pageSqlId = DEFAULT_PAGE_SQL_ID;
        }

        MappedStatement mappedStatement = (MappedStatement)
                metaStatementHandler.getValue("delegate.mappedStatement");
        // 只重寫需要分頁的sql語句。通過MappedStatement的ID匹配,默認重寫以Page結尾的MappedStatement的sql
        if (mappedStatement.getId().matches(pageSqlId)) {
            BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
            Object parameterObject = boundSql.getParameterObject();
            if (parameterObject == null) {
                throw new NullPointerException("parameterObject is null!");
            } else {
                String sql = boundSql.getSql();
                // 重寫sql
                String pageSql = sql + " LIMIT " + rowBounds.getOffset() + "," + rowBounds.getLimit();
                metaStatementHandler.setValue("delegate.boundSql.sql", pageSql);
                // 采用物理分頁后,就不需要mybatis的內存分頁了,所以重置下面的兩個參數
                metaStatementHandler.setValue("delegate.rowBounds.offset", RowBounds.NO_ROW_OFFSET);
                metaStatementHandler.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT);
            }
        }
        // 將執行權交給下一個攔截器
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        // 當目標類是StatementHandler類型時,才包裝目標類,否者直接返回目標本身,減少目標被代理的次數
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }

    @Override
    public void setProperties(Properties properties) {
        //To change body of implemented methods use File | Settings | File Templates.
    }

}

配置plugin

    <plugins>
        <plugin interceptor="plugin.PaginationInterceptor" />
    </plugins>

查詢SQL

    <!-- 測試分頁查詢 -->
    <select id="selectUserByPage" resultMap="dao.base.userResultMap">
        <![CDATA[
        SELECT * FROM user
        ]]>
    </select>

調用示例

    @Override
    public List<User> selectUserByPage(int offset, int limit) {
        RowBounds rowBounds = new RowBounds(offset, limit);
        return getSqlSession().selectList("dao.userdao.selectUserByPage", new Object(), rowBounds);
    }

 

 

另外,結合Spring MVC,編寫翻頁和生成頁碼代碼

頁碼類

package util;

/**
 * Created with IntelliJ IDEA.
 * User: zhenwei.liu
 * Date: 13-8-7
 * Time: 上午10:29
 * To change this template use File | Settings | File Templates.
 */
public class Pagination {
    private String url; // 頁碼url
    private int pageSize = 10;  // 每頁顯示記錄數
    private int currentPage = 1;    // 當前頁碼
    private int maxPage = Integer.MAX_VALUE;    // 最大頁數

    // 獲取offset
    public int getOffset() {
        return (currentPage - 1) * pageSize;
    }

    // 獲取limit
    public int getLimit() {
        return getPageSize();
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public int getPageSize() {
        return pageSize;
    }

    public void setPageSize(int pageSize) {
        this.pageSize = pageSize;
    }

    public int getCurrentPage() {
        return currentPage;
    }

    public void setCurrentPage(int currentPage) {
        if (currentPage < 1)
            currentPage = 1;
        if (currentPage > maxPage)
            currentPage = maxPage;
        this.currentPage = currentPage;
    }

    public int getMaxPage() {
        return maxPage;
    }

    public void setMaxPage(int maxPage) {
        this.maxPage = maxPage;
    }
}

 

為了計算最大頁碼,需要知道數據表的總記錄數,查詢SQL如下

    <!-- 記錄總數 -->
    <select id="countUser" resultType="Integer">
        <![CDATA[
        SELECT COUNT(*) FROM user
        ]]>
    </select>
    @Override
    public Integer countTable() {
        return getSqlSession().selectOne("dao.userdao.countUser");
    }

 

Controller中的使用

    @RequestMapping("/getUserByPage")
    public String getUserByPage(@RequestParam
                                    int page, Model model) {
        pagination.setCurrentPage(page);
        pagination.setUrl(getCurrentUrl());
        pagination.setMaxPage(userDao.countTable() / pagination.getPageSize() + 1);
        List<User> userList = userDao.selectUserByPage(
                pagination.getOffset(), pagination.getLimit());
        model.addAttribute(pagination);
        model.addAttribute(userList);
        return "index";
    }

 

 


免責聲明!

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



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