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的中对插件的实现采用了责任链模式。后面我们将分析具体的实现原理,欢迎交流。

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