MyBatis 外掛使用-自定義簡單的分頁外掛
阿新 • • 發佈:2019-09-28
目錄
- 1 分頁引數的傳遞
- 2 實現 Interceptor 介面
- 2.1 Interceptor 介面說明
- 2.1 註解說明
- 2.3 實現分頁介面 PageInterceptor
- 3. 更改配置
- 4 測試
@
作為一個優秀的框架, 其除了要解決大部分的流程之外, 還需要提供給使用者能夠自定義的能力。 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
中, 可以攔截的方法包括
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- 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;
攔截後
- 獲取分頁引數
- 根據引數改寫 sql
- 生成新的
MappedStatement
物件給代理方法 - 執行完成後移除分頁引數
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