当前位置:   article > 正文

Mybatis Interceptor 讲解

mybatis interceptor args的作用

Mybatis Interceptor 讲解

简介

Mybatis是比较流行的数据库持久层架构,可以很方便的与spring集成。框架比较轻量化,所以学习和上手的时间短,是一个不错的选择。 作为一个开源框架,Mybatis的设计值得称道。其中之一就是我们可以通过插件很方便的扩展Mybatis的功能。 下面我们通过一个简单的例子说明其工作原理。

基本结构

插件类首先必须实现org.apache.ibatis.plugin.Interceptor接口,如下:

  1. @Intercepts(
  2. //Signature定义被拦截的接口方法,可以有一个或多个。
  3. @Signature(
  4. //拦截的接口类型,支持 Executor,ParameterHandler,ResultSetHandler,StatementHandler
  5. //这里以Executor为例
  6. type = Executor.class,
  7. //Executor中的方法名
  8. method = "query",
  9. //Executor中query方法的参数类型
  10. args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
  11. ))
  12. public class ExamplePlugin implements Interceptor {
  13. @Override
  14. public Object intercept(Invocation invocation) throws Throwable {
  15. //我们可以在这里做一些扩展工作,但一定要在了解mybatis运行原理之后才能开发出你所期望的效果。
  16. System.out.println("example plugin ..." + invocation);
  17. //因为mybatis的使用责任链方式,这里一定要显示的调用proceed方法便调用能传递下去。
  18. return invocation.proceed();
  19. }
  20. @Override
  21. public Object plugin(Object target) {
  22. //判断是否是本拦截器需要拦截的接口类型,如果是增加代理
  23. if (target instanceof Executor) {
  24. return Plugin.wrap(target, this);
  25. }
  26. //如果不是返回源对象。
  27. else {
  28. return target;
  29. }
  30. }
  31. @Override
  32. public void setProperties(Properties properties) {
  33. //可以设置拦截器的属性,在这里我先忽略。
  34. }
  35. }

功能简介

Plugin.wrap(target, this)这句代码的功能是用我们的插件代理target对象。实现如下:

  1. public static Object wrap(Object target, Interceptor interceptor) {
  2. Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
  3. Class<?> type = target.getClass();
  4. Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
  5. if (interfaces.length > 0) {
  6. return Proxy.newProxyInstance(
  7. type.getClassLoader(),
  8. interfaces,
  9. new Plugin(target, interceptor, signatureMap));
  10. }
  11. return target;
  12. }

Plugin实现了InvocationHandler接口,通过jdk的代理机制把我们的插件(Interceptor)作为代理插入Mybatis的逻辑中。

我们通过Mapper执行查询的时候,插件会先于Mybatis的内部执行代码执行,其中起关键作用的是intercept方法,成功与否全靠它了。

要实现插件,必须先了解Invocation对象的属性,属性如下

  • private Object target 被代理对象
  • private Method method; mapper执行方法
  • private Object[] args; 参数,包括三个对象

args包括三个对象,分别是:

  • [0] MappedStatement
  • [1] 用户调用方法时传入的参数
  • [3] RowBounds,默认是RowBounds.DEFAULT

应用实例

我们以Mysql数据库分页为例,看插件是如何改变执行效果。

思路

Mybatis执行的sql是通过xml方式配置的,所以,如果我们要实现分页功能需要修改执行的sql,设置分页参数即可。说来容易做来难啊,直接上代码。

代码实现

  1. import org.apache.ibatis.builder.StaticSqlSource;
  2. import org.apache.ibatis.executor.Executor;
  3. import org.apache.ibatis.mapping.BoundSql;
  4. import org.apache.ibatis.mapping.MappedStatement;
  5. import org.apache.ibatis.mapping.ParameterMapping;
  6. import org.apache.ibatis.mapping.SqlSource;
  7. import org.apache.ibatis.plugin.*;
  8. import org.apache.ibatis.reflection.MetaObject;
  9. import org.apache.ibatis.reflection.SystemMetaObject;
  10. import org.apache.ibatis.scripting.defaults.RawSqlSource;
  11. import org.apache.ibatis.session.Configuration;
  12. import org.apache.ibatis.session.ResultHandler;
  13. import org.apache.ibatis.session.RowBounds;
  14. import java.util.*;
  15. /**
  16. * Mysql分頁插件
  17. * Created by WangHuanyu on 2015/11/5.
  18. */
  19. @Intercepts(
  20. //Signature定义被拦截的接口方法,可以有一个或多个。
  21. @Signature(
  22. //拦截的接口类型,支持 Executor,ParameterHandler,ResultSetHandler,StatementHandler
  23. //这里以Executor为例
  24. type = Executor.class,
  25. //Executor中的方法名
  26. method = "query",
  27. //Executor中query方法的参数类型
  28. args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
  29. ))
  30. public class ExamplePagePlugin implements Interceptor {
  31. //分页的id后缀
  32. String SUFFIX_PAGE = "_PageHelper";
  33. //count查询的id后缀
  34. String SUFFIX_COUNT = SUFFIX_PAGE + "_Count";
  35. //第一个分页参数
  36. String PAGEPARAMETER_FIRST = "First" + SUFFIX_PAGE;
  37. //第二个分页参数
  38. String PAGEPARAMETER_SECOND = "Second" + SUFFIX_PAGE;
  39. String PROVIDER_OBJECT = "_provider_object";
  40. //存储原始的参数
  41. String ORIGINAL_PARAMETER_OBJECT = "_ORIGINAL_PARAMETER_OBJECT";
  42. @Override
  43. public Object intercept(Invocation invocation) throws Throwable {
  44. //我们可以在这里做一些扩展工作,但一定要在了解mybatis运行原理之后才能开发出你所期望的效果。
  45. System.out.println("example plugin ..." + invocation);
  46. final Object[] args = invocation.getArgs();
  47. MappedStatement ms = (MappedStatement) args[0];
  48. MetaObject msObject = SystemMetaObject.forObject(ms);
  49. BoundSql boundSql = ms.getBoundSql(args[1]);
  50. SqlSource sqlSource = ms.getSqlSource();
  51. SqlSource tempSqlSource = sqlSource;
  52. SqlSource pageSqlSource;
  53. //實例中只演示RowSqlSource
  54. if (tempSqlSource instanceof RawSqlSource) {
  55. pageSqlSource = new PageRawSqlSource((RawSqlSource) tempSqlSource);
  56. } else {
  57. throw new RuntimeException("无法处理该类型[" + sqlSource.getClass() + "]的SqlSource");
  58. }
  59. msObject.setValue("sqlSource", pageSqlSource);
  60. //添加分頁參數
  61. args[1] = setPageParameter(ms, args[1], boundSql, 6, 5);
  62. //执行分页查询
  63. //因为mybatis的使用责任链方式,这里一定要显示的调用proceed方法便调用能传递下去。
  64. return invocation.proceed();
  65. }
  66. @Override
  67. public Object plugin(Object target) {
  68. //判断是否是本拦截器需要拦截的接口类型,如果是增加代理
  69. if (target instanceof Executor) {
  70. return Plugin.wrap(target, this);
  71. }
  72. //如果不是返回源对象。
  73. else {
  74. return target;
  75. }
  76. }
  77. @SuppressWarnings({"unchecked", "varargs"})
  78. public Map setPageParameter(MappedStatement ms, Object parameterObject, BoundSql boundSql, int startrow, int pagesize) {
  79. Map paramMap = processParameter(ms, parameterObject, boundSql);
  80. paramMap.put(PAGEPARAMETER_FIRST, startrow);
  81. paramMap.put(PAGEPARAMETER_SECOND, pagesize);
  82. return paramMap;
  83. }
  84. public Map<String, Object> processParameter(MappedStatement ms, Object parameterObject, BoundSql boundSql) {
  85. Map<String, Object> paramMap = null;
  86. if (parameterObject == null) {
  87. paramMap = new HashMap<String, Object>();
  88. } else if (parameterObject instanceof Map) {
  89. //解决不可变Map的情况
  90. paramMap = new HashMap<String, Object>();
  91. paramMap.putAll((Map<String, Object>) parameterObject);
  92. } else {
  93. paramMap = new HashMap<String, Object>();
  94. //动态sql时的判断条件不会出现在ParameterMapping中,但是必须有,所以这里需要收集所有的getter属性
  95. //TypeHandlerRegistry可以直接处理的会作为一个直接使用的对象进行处理
  96. boolean hasTypeHandler = ms.getConfiguration().getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass());
  97. MetaObject metaObject = SystemMetaObject.forObject(parameterObject);
  98. if (!hasTypeHandler) {
  99. for (String name : metaObject.getGetterNames()) {
  100. paramMap.put(name, metaObject.getValue(name));
  101. }
  102. }
  103. //下面这段方法,主要解决一个常见类型的参数时的问题
  104. if (boundSql.getParameterMappings() != null && boundSql.getParameterMappings().size() > 0) {
  105. for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
  106. String name = parameterMapping.getProperty();
  107. if (!name.equals(PAGEPARAMETER_FIRST)
  108. && !name.equals(PAGEPARAMETER_SECOND)
  109. && paramMap.get(name) == null) {
  110. if (hasTypeHandler
  111. || parameterMapping.getJavaType().equals(parameterObject.getClass())) {
  112. paramMap.put(name, parameterObject);
  113. break;
  114. }
  115. }
  116. }
  117. }
  118. }
  119. //备份原始参数对象
  120. paramMap.put(ORIGINAL_PARAMETER_OBJECT, parameterObject);
  121. return paramMap;
  122. }
  123. @Override
  124. public void setProperties(Properties properties) {
  125. //可以设置拦截器的属性,在这里我先忽略。
  126. }
  127. public class PageRawSqlSource implements SqlSource {
  128. private SqlSource sqlSource;
  129. private String sql;
  130. private List<ParameterMapping> parameterMappings;
  131. private Configuration configuration;
  132. private SqlSource original;
  133. public PageRawSqlSource(RawSqlSource rawSqlSource) {
  134. this.original = rawSqlSource;
  135. MetaObject metaObject = SystemMetaObject.forObject(rawSqlSource);
  136. StaticSqlSource staticSqlSource = (StaticSqlSource) metaObject.getValue("sqlSource");
  137. metaObject = SystemMetaObject.forObject(staticSqlSource);
  138. this.sql = (String) metaObject.getValue("sql");
  139. this.parameterMappings = (List<ParameterMapping>) metaObject.getValue("parameterMappings");
  140. this.configuration = (Configuration) metaObject.getValue("configuration");
  141. this.sqlSource = staticSqlSource;
  142. }
  143. @Override
  144. public BoundSql getBoundSql(Object parameterObject) {
  145. String tempSql = sql;
  146. tempSql = getPageSql(tempSql);
  147. return new BoundSql(configuration, tempSql, getPageParameterMapping(configuration, original.getBoundSql(parameterObject)), parameterObject);
  148. }
  149. public String getPageSql(String sql) {
  150. StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);
  151. sqlBuilder.append(sql);
  152. sqlBuilder.append(" limit ?,?");
  153. return sqlBuilder.toString();
  154. }
  155. public List<ParameterMapping> getPageParameterMapping(Configuration configuration, BoundSql boundSql) {
  156. List<ParameterMapping> newParameterMappings = new ArrayList<ParameterMapping>();
  157. if (boundSql != null && boundSql.getParameterMappings() != null) {
  158. newParameterMappings.addAll(boundSql.getParameterMappings());
  159. }
  160. newParameterMappings.add(new ParameterMapping.Builder(configuration, PAGEPARAMETER_FIRST, Integer.class).build());
  161. newParameterMappings.add(new ParameterMapping.Builder(configuration, PAGEPARAMETER_SECOND, Integer.class).build());
  162. return newParameterMappings;
  163. }
  164. }
  165. }

讲解

本实例只是作为分页演示,所以参数硬编码为args[1] = setPageParameter(ms, args[1], boundSql, 6, 5);。 真实环境可以用Threadlocal传参。这里就不做演示了。

  • 修改sql

MappedStatement中获取SqlSource,如果是RawSqlSource类型,我就创建一个PageRawSqlSource对象。 在PageRawSqlSource的构造方法中,我们从RawSqlSource中获取要需要的信息。

在执行时,Mybatis在获取sql时我们通过getPageSql方法增加分页语句。 通过getPageParameterMapping方法增加分页参数。创建一个新的BoundSql对象,返回。

  • 添加sql参数

修改args[1],值设置为setPageParameter方法的返回值。

至此,我们就可以通过Mapper进行分页查询了。

转载于:https://my.oschina.net/MrW/blog/527297

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号