MyBatis 插件使用-自定義簡單的分頁插件


@


作為一個優秀的框架, 其除了要解決大部分的流程之外, 還需要提供給使用者能夠自定義的能力。 MyBatis 有緩存, 有插件接口等。我們可以通過自定義插件的方式來對 MyBatis 進行使用上的擴展。

以一個簡單的 mysql 分頁插件為例, 插件的使用包含以下步驟:

1 分頁參數的傳遞

分頁參數就是 offset 和 limit。 可以使用 RowBounds 來進行傳遞, 但是這樣需要對原有的方法進行修改。 因此, 本例子通過 ThreadLocal 進行無痛覺的傳遞。

/**
 * @author homejim
 */
public class PageUtil {
    private static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();

    public static void setPagingParam(int offset, int limit) {
        Page page = new Page(offset, limit);
        LOCAL_PAGE.set(page);
    }

    public static void removePagingParam() {
        LOCAL_PAGE.remove();
    }

    public static Page getPaingParam() {
        return LOCAL_PAGE.get();
    }

}

在實際的使用過程中, 用戶只需要再調用之前使用 PageUtil#setPagingParam 方法來進行分頁參數的傳遞即可。 后續無需進行處理。

2 實現 Interceptor 接口

2.1 Interceptor 接口說明

先看看攔截器的接口。


/**
 * 攔截器接口
 *
 * @author Clinton Begin
 */
public interface Interceptor {

  /**
   * 執行攔截邏輯的方法
   *
   * @param invocation 調用信息
   * @return 調用結果
   * @throws Throwable 異常
   */
  Object intercept(Invocation invocation) throws Throwable;

  /**
   * 代理類
   *
   * @param target
   * @return
   */
  Object plugin(Object target);

  /**
   * 根據配置來初始化 Interceptor 方法
   * @param properties
   */
  void setProperties(Properties properties);

}

因此, 在實際的使用中。我們要覆蓋這幾個方法。

2.1 注解說明

mybatis 中, 可以攔截的方法包括

  1. Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  2. ParameterHandler (getParameterObject, setParameters)
  3. ResultSetHandler (handleResultSets, handleOutputParameters)
  4. StatementHandler (prepare, parameterize, batch, update, query)

但是接口只有一個 Interceptor, 因此, 需要使用注解 @Intercepts@Signature 來指定攔截的方法。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Intercepts {
    Signature[] value();
}

Intercepts 注解中是 Signature 注解的數組。

/**
 * 方法簽名信息
 *
 * @author Clinton Begin
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
  /**
   * 需要攔截的類型
   *
   * @return
   */
  Class<?> type();

  /**
   * 需要攔截的方法
   * @return
   */
  String method();

  /**
   * 被攔截方法的參數列表
   *
   * @return
   */
  Class<?>[] args();
}

2.3 實現分頁接口 PageInterceptor


/**
 * 分頁插件
 *
 * @author homejim
 */
@Intercepts({
        @Signature(
                type = Executor.class,
                method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
        )
})
@Slf4j
public class PageInterceptor implements Interceptor {

    private static int MAPPEDSTATEMENT_INDEX = 0;

    private static int PARAMETER_INDEX = 1;

    private static int ROWBOUNDS_INDEX = 2;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        // 從 Invocation 中獲取參數
        final Object[] queryArgs = invocation.getArgs();
        final MappedStatement ms = (MappedStatement) queryArgs[MAPPEDSTATEMENT_INDEX];
        final Object parameter = queryArgs[PARAMETER_INDEX];

        //  獲取分頁參數
        Page paingParam = PageUtil.getPaingParam();
        if (paingParam != null) {

            // 構造新的 sql, select xxx from xxx where yyy limit offset,limit
            final BoundSql boundSql = ms.getBoundSql(parameter);
            String pagingSql = getPagingSql(boundSql.getSql(), paingParam.getOffset(), paingParam.getLimit());

            // 設置新的 MappedStatement
            BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), pagingSql,
                    boundSql.getParameterMappings(), boundSql.getParameterObject());
            MappedStatement mappedStatement = newMappedStatement(ms, newBoundSql);
            queryArgs[MAPPEDSTATEMENT_INDEX] = mappedStatement;

            // 重置 RowBound
            queryArgs[ROWBOUNDS_INDEX] = new RowBounds(RowBounds.NO_ROW_OFFSET, RowBounds.NO_ROW_LIMIT);
        }
        Object result = invocation.proceed();
        PageUtil.removePagingParam();
        return result;
    }

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

    @Override
    public void setProperties(Properties properties) {

    }

    /**
     * 創建 MappedStatement
     * @param ms
     * @param newBoundSql
     * @return
     */
    private MappedStatement newMappedStatement(MappedStatement ms, BoundSql newBoundSql) {
        MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(),
                new BoundSqlSqlSource(newBoundSql), ms.getSqlCommandType());
        builder.keyColumn(delimitedArrayToString(ms.getKeyColumns()));
        builder.keyGenerator(ms.getKeyGenerator());
        builder.keyProperty(delimitedArrayToString(ms.getKeyProperties()));
        builder.lang(ms.getLang());
        builder.resource(ms.getResource());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.resultOrdered(ms.isResultOrdered());
        builder.resultSets(delimitedArrayToString(ms.getResultSets()));
        builder.resultSetType(ms.getResultSetType());
        builder.timeout(ms.getTimeout());
        builder.statementType(ms.getStatementType());
        builder.useCache(ms.isUseCache());
        builder.cache(ms.getCache());
        builder.databaseId(ms.getDatabaseId());
        builder.fetchSize(ms.getFetchSize());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        return builder.build();
    }

    public String getPagingSql(String sql, int offset, int limit) {
        StringBuilder result = new StringBuilder(sql.length() + 100);
        result.append(sql).append(" limit ");

        if (offset > 0) {
            result.append(offset).append(",").append(limit);
        }else{
            result.append(limit);
        }
        return result.toString();
    }

    public String delimitedArrayToString(String[] array) {

        if (array == null || array.length == 0) {
            return "";
        }
        Joiner joiner = Joiner.on(",");
        return joiner.join(array);
    }
}

根據前面注解的講解, 我們要攔截的是 Executor 類中以下方法

<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

攔截后

  1. 獲取分頁參數
  2. 根據參數改寫 sql
  3. 生成新的 MappedStatement 對象給代理方法
  4. 執行完成后移除分頁參數

3. 更改配置

在以上的步驟之后, mybatis 還是不知道我們都有哪些接口, 以及哪些接口需要用。 因此, 需要再配置中進行說明。

mybatis-config.xml 文件中, 加入以下的配置

<plugins>
    <plugin interceptor="com.homejim.mybatis.plugin.PageInterceptor">
    </plugin>
</plugins>

4 測試

    @Test
    public void testSelectList() {
        SqlSession sqlSession = null;
        try {
            sqlSession = sqlSessionFactory.openSession();
            PageUtil.setPagingParam(1, 2);
            List<Student> students = sqlSession.selectList("selectAll");
            for (int i = 0; i < students.size(); i++) {
                System.out.println(students.get(i));
            }

            List<Student> students2 = sqlSession.selectList("selectAll");
            for (int i = 0; i < students2.size(); i++) {
                System.out.println(students2.get(i));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (sqlSession != null) {
                sqlSession.close();
            }
        }
    }

其中, 第一個查詢使用了分頁, 第二個沒有使用。 執行結果如下
分頁效果
第一個查詢使用了分頁, 因此有 limit , 第二個查詢沒有, 因此查詢出了所有的結果。

更多使用, 訪問我的GitHub項目


免責聲明!

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



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