@
作为一个优秀的框架, 其除了要解决大部分的流程之外, 还需要提供给使用者能够自定义的能力。 MyBatis
有缓存, 有插件接口等。我们可以通过自定义插件的方式来对 MyBatis
进行使用上的扩展。
以一个简单的 mysql 分页插件为例, 插件的使用包含以下步骤:
分页参数就是 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
方法来进行分页参数的传递即可。 后续无需进行处理。
先看看拦截器的接口。
/** * 拦截器接口 * * @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); }
因此, 在实际的使用中。我们要覆盖这几个方法。
在 mybatis
中, 可以拦截的方法包括
但是接口只有一个 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(); }
/** * 分页插件 * * @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;
拦截后
MappedStatement
在以上的步骤之后, mybatis 还是不知道我们都有哪些接口, 以及哪些接口需要用。 因此, 需要再配置中进行说明。
在 mybatis-config.xml
文件中, 加入以下的配置
<plugins> <plugin interceptor="com.homejim.mybatis.plugin.PageInterceptor"> </plugin> </plugins>
@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 项目