1. 程式人生 > >Mybatis自動分頁外掛

Mybatis自動分頁外掛

自己實現了一個比較簡單的Mybatis分頁外掛。在講解如何實現分頁外掛之前,我們先簡單介紹一下Mybatis中的一些重要的物件。我們通過對映器Mapper對資料庫進行增刪改操作時,Mapper執行的過程是通過Executor、StatementHandler、ParameterHandler和ResultHandler來完成對資料庫的操作和返回結果的。

  • Executor代表執行器,由它來排程StatementHandler、ParameterHandler、ResultHandler等來執行對應的SQL。
  • StatementHandler的作用是使用資料庫的Statement(PreparedStatement)執行操作,是上面提到的四個物件的核心。
  • ParameterHandler用於SQL對引數的處理。
  • ResultHandler是進行最後資料集(ResultSet)的封裝返回處理的。

要編寫Mybatis外掛,我們就必須要實現Interceptor介面,下面先來看看這個接口裡面的方法:

public interface Interceptor {
    Object intercept(Invocation var1) throws Throwable;

    Object plugin(Object var1);

    void setProperties(Properties var1);
}
  • intercept方法是外掛的核心方法,它有個Invocation型別的引數,通過這個引數可以反射排程原來物件的方法。
  • plugin方法的作用是給被攔截的物件生成一個代理物件並返回。
  • setProperties方法允許在plugin元素中配置所需引數。

攔截器簽名

@Intercepts({@Signature(
        type = Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
)})

這裡我要攔截的是Executor的query方法,先判斷有沒有PageParam型別的分頁引數,如果有的話先查詢符合條件的資料條數count,再獲取具體的資料list,將count和list封裝在Page型別的物件裡面返回。

@Intercepts({@Signature(
        type = Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
)})
public class EasyPage implements Interceptor {

    Logger logger = LoggerFactory.getLogger(EasyPage.class);

    private static int MAPPED_STATEMENT_INDEX = 0;
    private static int PARAMETER_INDEX = 1;

    public Object intercept(Invocation invocation) throws Throwable {
        Object[] queryArgs = invocation.getArgs();
        MappedStatement ms = (MappedStatement) queryArgs[MAPPED_STATEMENT_INDEX];
        Object parameter = queryArgs[PARAMETER_INDEX];

        PageParam page = new PageParam();
        String pageKey = "";// 分頁引數字首
        if (parameter instanceof PageParam) {// 只有分頁引數一個引數
            page = (PageParam) parameter;
        } else if (parameter instanceof PageParam || parameter instanceof HashMap) {// 2個及以上引數
            HashMap<String, Object> parameterMap = (HashMap<String, Object>) parameter;
            for (String key : parameterMap.keySet()) {
                if (parameterMap.get(key) instanceof PageParam) {
                    page = (PageParam) parameterMap.get(key);
                    pageKey = key + ".";
                    break;
                }
            }
        }

        // 判斷是否需要分頁,當引數不是預設值的時候就進行分頁
        if (page != null && page.getIndex() != 0 && page.getRows() != Integer.MAX_VALUE) {
            int index = page.getIndex();
            int rows = page.getRows();

            BoundSql boundSql = ms.getBoundSql(parameter);
            int total = this.getCount(ms, parameter, boundSql);
            List list = Collections.EMPTY_LIST;
            if (total > 0) {
                Dialect dialect = new Dialect();
                BoundSql newBoundSql = dialect.getBoungSQL(ms, boundSql, (index - 1) * rows, pageKey);

                MappedStatement newMs = copyFromMappedStatement(ms, new MySqlSource(newBoundSql));
                queryArgs[MAPPED_STATEMENT_INDEX] = newMs;
                list = (List) invocation.proceed();
            }
            return new Page(list, index, rows, total);
        }
        return invocation.proceed();
    }
/**
     * 獲取資料總條數
     * @param mappedStatement
     * @param parameter
     * @param boundSql
     * @return
     * @throws SQLException
     */
    public int getCount(MappedStatement mappedStatement, Object parameter, BoundSql boundSql) throws SQLException {
        StringBuilder sqlBuilder = new StringBuilder();
        sqlBuilder.append("select count(1) from (");
        sqlBuilder.append(clearOrderBy(boundSql.getSql())).append(") tmp");

        Connection connection;
        PreparedStatement countStmt = null;
        ResultSet rs = null;
        int count = 0;
        try {
            connection = mappedStatement.getConfiguration().getEnvironment().getDataSource().getConnection();
            countStmt = connection.prepareStatement(sqlBuilder.toString());
            DefaultParameterHandler handler = new DefaultParameterHandler(mappedStatement, parameter, boundSql);
            handler.setParameters(countStmt);
            rs = countStmt.executeQuery();
            if (rs.next()) {
                count = rs.getInt(1);
            }
            logger.debug("==> Preparing: {}", sqlBuilder.toString());
            logger.debug("<== Total: {}", count);
        } finally {
            try {
                if (rs != null) {
                    rs.close();
                }
            } finally {
                if (countStmt != null) {
                    countStmt.close();
                }
            }
        }
        return count;
    }
/**
     * 根據資料庫型別設定引數,不需要在配置中設定資料庫型別,通過DatabaseMetaData物件可以取到資料庫名稱
     * @param ms
     * @param boundSql
     * @param offset
     * @param pageKey
     * @return
     * @throws SQLException
     */
    public BoundSql getBoungSQL(MappedStatement ms, BoundSql boundSql, int offset, String pageKey) throws SQLException {
        DatabaseMetaData dbmd = ms.getConfiguration().getEnvironment().getDataSource().getConnection().getMetaData();
        String dbType = dbmd.getDatabaseProductName();

        String sql = boundSql.getSql();
        if (dbType != null) {
            switch (dbType) {
                case "MySQL":
                    sql = MysqlDialect.getLimitString(boundSql.getSql(), offset);
                    break;
                case "Oracle":
                    sql = OracleDialect.getLimitString(boundSql.getSql(), offset);
                    break;
                default:
                    throw new IllegalArgumentException("Not supported dialect:" + dbType);
            }
        }

        // copy a new list, if use "list=boundSql.getParameterMappings()" will throws UnsupportedOperationException
        List<ParameterMapping> list = new ArrayList<ParameterMapping>(boundSql.getParameterMappings());
        if (offset > 0) {
            list.add(new ParameterMapping.Builder(ms.getConfiguration(), pageKey + "offset", Integer.class).build());
            list.add(new ParameterMapping.Builder(ms.getConfiguration(), pageKey + "rows", Integer.class).build());
        } else {
            list.add(new ParameterMapping.Builder(ms.getConfiguration(), pageKey + "rows", Integer.class).build());
        }

        BoundSql newboundSql = new BoundSql(ms.getConfiguration(), sql, list, boundSql.getParameterObject());
        return newboundSql;
    }

這裡寫圖片描述