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
中, 可以拦截的方法包括
- 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项目