Mybatis工具--分頁插件

1 概述

在我們使用Mybatis的時候希望對以映射語句進行攔截處理,這個時候就可以用到Mybatis的插件。接下來我們就來Mybatis中的插件是如何實現的,然後來實現分頁插件。

2 實現

MyBatis 允許使用插件來攔截的方法調用包括: 
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) 
ParameterHandler (getParameterObject, setParameters) 
ResultSetHandler (handleResultSets, handleOutputParameters) 
StatementHandler (prepare, parameterize, batch, update, query) 

如果要實現Mybatis的插件,需要實現Mybatis的Interceptor 接口。下面我們就來看一下分頁插件的具體實現吧。

3 分頁插件

(1)插件核心代碼

import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.Configuration;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

/**
 * 分頁插件
 *
 * @author: LIUTAO
 * @Date: Created in 2018/11/6  11:26
 * @Modified By:
 */
@Intercepts({
        @Signature(type = StatementHandler.class,
                method = "prepare",
                args = {Connection.class})})
public class PagingPlugin implements Interceptor {
    private Integer defaultPage;
    private Integer defaultPageSize;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler stmtHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaStatementHandler = SystemMetaObject.forObject(stmtHandler);

        //判斷是否是查詢語句
        if (checkIsSelect(metaStatementHandler)) {
            return invocation.proceed();
        }

        BoundSql boundSql = stmtHandler.getBoundSql();
        Object parameterObject = boundSql.getParameterObject();

        //獲取到分頁數據
        PageParams pageParams;
        if (parameterObject instanceof PageParams) {
            pageParams = (PageParams) parameterObject;
        }else {
            return invocation.proceed();
        }

        //獲取參數
        Boolean useFlag = pageParams.getUseFlag();
        if (!useFlag) {
            return invocation.proceed();
        }

        Integer pageNum = pageParams.getPageNum() == null ? this.defaultPage : pageParams.getPageNum();
        Integer pageSize = pageParams.getPageSize() == null ? this.defaultPageSize : pageParams.getPageSize();

        Connection connection = (Connection) invocation.getArgs()[0];
        int total = getTotal(connection, metaStatementHandler, boundSql);
        int totalPage = total % pageSize == 0 ? total / pageSize : total / pageSize + 1;
        pageParams.setTotalNums(total);
        pageParams.setTotalPage(totalPage);

        //檢查當前頁碼的有效性
        checkPage(pageNum, totalPage);

        //修改sql
        changeSql(metaStatementHandler, pageNum, pageSize);

        return invocation.proceed();
    }

    /**
     * 判斷是否是查詢語句
     * @param metaStatementHandler
     * @return
     */
    private boolean checkIsSelect(MetaObject metaStatementHandler) {
        SqlCommandType sqlCommandType = (SqlCommandType) metaStatementHandler.getValue("delegate.mappedStatement.sqlCommandType");
        if (!sqlCommandType.equals(SqlCommandType.SELECT)) {
            return true;
        }
        return false;
    }

    @Override
    public Object plugin(Object statementHandler) {
        return Plugin.wrap(statementHandler, this);
    }

    @Override
    public void setProperties(Properties properties) {
        String strDefaultPage = properties.getProperty("defaultPage", "1");
        String strDefaultPageSize = properties.getProperty("defaultPageSize", "5");
        this.defaultPage = Integer.parseInt(strDefaultPage);
        this.defaultPageSize = Integer.parseInt(strDefaultPageSize);
    }

    /**
     * 檢測當前頁碼的有效性
     *
     * @param pageNum
     * @param pageTotal
     */
    private void checkPage(Integer pageNum, Integer pageTotal) {
        if (pageNum > pageTotal) {
            throw new PageException("pageNum is bigger than pageTotal!");
        }
    }

    /**
     * 修改當前查詢的sql
     *
     * @param metaObject
     * @param page
     * @param pageSize
     */
    private void changeSql(MetaObject metaObject, int page, int pageSize) {

        //獲取當前需要執行的SQL
        String sql = (String) metaObject.getValue("delegate.boundSql.sql");
        sql = sql.substring(0, sql.length() - 1);
        String newSql = sql + " limit " + (page - 1) * pageSize + ", " + pageSize + ";";

        //修改當前需要執行的sql
        metaObject.setValue("boundSql.sql", newSql);
    }

    /**
     * 獲取總數
     *
     * @param connection
     * @param metaObject
     * @param boundSql
     * @return
     */
    private int getTotal(Connection connection, MetaObject metaObject, BoundSql boundSql) throws SQLException {
        //獲取當前mappedStatement
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");

        //獲取配置對象
        Configuration configuration = mappedStatement.getConfiguration();

        //獲取當前需要執行的sql
        String sql = boundSql.getSql();
        sql = sql.substring(0, sql.length() - 1);
        String countSql = "select count(*) total from (" + sql + ")  $proxytable ;";

        //獲取攔截方法參數
        PreparedStatement ps = null;
        int total = 0;
        try {
            ps = connection.prepareStatement(countSql);
            BoundSql countBoundSql = new BoundSql(configuration, countSql, boundSql.getParameterMappings(), boundSql.getParameterObject());
            ParameterHandler handler = new DefaultParameterHandler(mappedStatement, boundSql.getParameterObject(), countBoundSql);
            handler.setParameters(ps);
            ResultSet resultSet = ps.executeQuery();
            while (resultSet.next()) {
                total = resultSet.getInt("total");
            }
        } catch (SQLException e) {

            if (ps != null && ps.isClosed()) {
                ps.close();
            }
            e.printStackTrace();
        }
        return total;
    }
}

(2)分頁異常類

/**
 * 分頁異常
 *
 * @author: LIUTAO
 * @Date: Created in 2018/11/6  11:34
 * @Modified By:
 */
public class PageException extends RuntimeException {
    public PageException(String message) {
        super(message);
    }
}

(3)分頁數據封裝

/**
 * 分頁數據封裝
 *
 * @author: LIUTAO
 * @Date: Created in 2018/11/6  14:14
 * @Modified By:
 */
public abstract class PageParams {
    private Integer pageNum = 1;
    private Integer pageSize = 5;
    private Boolean useFlag = true;
    private Integer totalNums;
    private Integer totalPage;

    public Integer getPageNum() {
        return pageNum;
    }

    public void setPageNum(Integer pageNum) {
        this.pageNum = pageNum;
    }

    public Integer getPageSize() {
        return pageSize;
    }

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

    public Boolean getUseFlag() {
        return useFlag;
    }

    public void setUseFlag(Boolean useFlag) {
        this.useFlag = useFlag;
    }

    public Integer getTotalNums() {
        return totalNums;
    }

    public void setTotalNums(Integer totalNums) {
        this.totalNums = totalNums;
    }

    public Integer getTotalPage() {
        return totalPage;
    }

    public void setTotalPage(Integer totalPage) {
        this.totalPage = totalPage;
    }

    @Override
    public String toString() {
        return "PageParams{" +
                "pageNum=" + pageNum +
                ", pageSize=" + pageSize +
                ", useFlag=" + useFlag +
                ", totalNums=" + totalNums +
                ", totalPage=" + totalPage +
                '}';
    }
}

(4)插件配置

<plugins>
        <plugin interceptor="com.liutao.mybatis.util.PagingPlugin">
            <property name="defaultPage" value="1" />
            <property name="defaultPageSize" value="5" />
        </plugin>
</plugins>

當我們使用的時候,只需要將我們的查詢參數繼承PageParams就可以實現數據的分頁查找。這裏順便提一下關於Mybatis的中對插件的實現採用了責任鏈模式。後面我們將分析具體的實現原理,歡迎交流。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章