Mybatis 插件使用及源码分析

守听  金牌会员 | 2022-9-16 17:18:57 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 895|帖子 895|积分 2685

Mybatis 插件

Mybatis插件主要是通过JDK动态代理实现的,插件可以针对接口中的方法进行代理增强,在Mybatis中比较重要的接口如下:

  • Executor :sql执行器,包含多个实现类,比如SimpleExecutor
  • StatementHander: sql语句处理器,用于将sql语句与Statement的映射,实现类有:PrepareStatementHandler、SimpleStatementHandler、CallBackStatementHandler
  • ParameterHandler:用于参数处理,将传入的参数一一的解析并将类型解析出来,会用到TypeHandler,最终这些数据会用于StatementHandler进行数据的映射,比如?对应的值的映射
  • ResultSetHandler:结果值的处理器,用于数据在查询出来之后,将数据通过ResultSet把数据映射给返回值类型的类上,通过反射(内省)处理映射数据

Mybatis插件的使用

Mybatis插件使用通过@Intercepts注解进行接口的绑定,如下定义一个插件类
  1. /**
  2. * @author <a target="_blank" href="https://www.cnblogs.com/2360564660@qq.com">redwinter</a>
  3. * @since 1.0
  4. **/
  5. @Intercepts({@Signature(
  6.         type = StatementHandler.class,
  7.         method = "prepare",
  8.         args = {Connection.class, Integer.class}
  9. )})
  10. @Slf4j
  11. public class MyPlugin implements Interceptor {
  12.     @Override
  13.     public Object intercept(Invocation invocation) throws Throwable {
  14.         log.info("对方法进行增强....");
  15.         return invocation.proceed();
  16.     }
  17.     @Override
  18.     public Object plugin(Object target) {
  19.         return Plugin.wrap(target, this);
  20.     }
  21.     @Override
  22.     public void setProperties(Properties properties) {
  23.         log.info("获取属性值:{}", properties);
  24.     }
  25. }
复制代码
然后需要将定义的插件配置mybatis的配置文件中:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
  3. <configuration>
  4.    
  5.     <properties resource="db.properties"/>
  6.    
  7.     <settings>
  8.         
  9.         <setting name="logImpl" value="STDOUT_LOGGING" />
  10.         
  11.         <setting name="cacheEnabled" value="true"/>
  12.     </settings>
  13.     <typeAliases>
  14.         <package name="com.redwinter.study.mybatis.model"/>
  15.     </typeAliases>
  16.        
  17.     <plugins>
  18.         <plugin interceptor="mybatis.plugins.MyPlugin">
  19.             <property name="redwinter" value="冬玲"/>
  20.         </plugin>
  21.         <plugin interceptor="com.github.pagehelper.PageInterceptor">
  22.             
  23.         </plugin>
  24.     </plugins>
  25.    
  26.     <environments default="development">
  27.         <environment id="development">
  28.             <transactionManager type="JDBC" />
  29.             
  30.             <dataSource type="POOLED">
  31.                
  32.                 <property name="driver" value="${driver}" />
  33.                 <property name="url" value="${url}" />
  34.                 <property name="username" value="${name}" />
  35.                 <property name="password" value="${password}" />
  36.             </dataSource>
  37.         </environment>
  38.     </environments>
  39.     <mappers>
  40.         <package name="mybatis.mapper"/>
  41.     </mappers>
  42. </configuration>
复制代码
这样就可以生效了,当我们执行数据查询的时候,只要是执行了StatementHandler#prepare方法,那么都会执行到自定的逻辑增强
日志如下:
  1. Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@1386958]
  2. 16:02:38.260 [main] INFO mybatis.plugins.MyPlugin - 对方法进行增强....
  3. ==>  Preparing: update user set name = ?, age = ? where id = ?
  4. ==> Parameters: 李四(String), 19(Integer), 1(Integer)
  5. <==    Updates: 1
  6. Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@1386958]
  7. Cache Hit Ratio [mybatis.mapper.UserMapper]: 0.5
  8. 16:02:38.303 [main] INFO mybatis.plugins.MyPlugin - 对方法进行增强....
  9. ==>  Preparing: select * from user where id = ?
  10. ==> Parameters: 1(Integer)
  11. <==    Columns: id, age, name
  12. <==        Row: 1, 19, 李四
  13. <==      Total: 1
  14. false
复制代码
这样的话就完成了拦截器插件的代理对象的创建,这里创建出来的代理对象就是StatementHandler,在前面自定义的插件,配置的是拦截StatementHandler#prepare方法,那么在哪里执行的呢?
回到Executor接口实现类SimpleExecutor了中doQuery方法,这个方法中会去创建一个预编译SQL处理器,执行prepareStatement方法:
  1. public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  2.     try {
  3.       // 创建一个解析xml的构建器,构造函数中会创建一个Configuration类
  4.       XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
  5.       // 解析xml配置
  6.       return build(parser.parse());
  7.     } catch (Exception e) {
  8.       throw ExceptionFactory.wrapException("Error building SqlSession.", e);
  9.     } finally {
  10.       ErrorContext.instance().reset();
  11.       try {
  12.               if (inputStream != null) {
  13.                 inputStream.close();
  14.               }
  15.       } catch (IOException e) {
  16.         // Intentionally ignore. Prefer previous error.
  17.       }
  18.     }
  19.   }
复制代码
这里的话就会调用prepare方法,这个方法就是自定义插件配置需要拦截的方法,由于这个handler是一个代理对象,我们都知道只要是代理对象,只要执行代理对象的任何方法都会去执行InvoketionHandler接口的invoke方法,当执行到这个方法的时候就会调用到我们自定义的插件类中intercept方法:
  1. private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
  2.     super(new Configuration());
  3.     ErrorContext.instance().resource("SQL Mapper Configuration");
  4.     this.configuration.setVariables(props);
  5.     this.parsed = false;
  6.     this.environment = environment;
  7.     this.parser = parser;
  8.   }
复制代码
所以只要我们执行了sql查询,那么都会通过JDK动态代理创建的代理对象去执行到这个增强方法。
插件的扩展

在Mybatis中有个分页的插件叫PageHelper,这个插件就是使用了Mybatis插件机制完成的,当然还有比如早期的TkMapper插件。接下来分析一下PageHelper是如何实现分页机制的。
引入依赖:
  1. public Configuration() {
  2.     // 添加别名
  3.     typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
  4.     typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
  5.     typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
  6.     typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
  7.     typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
  8.     typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
  9.     typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
  10.     typeAliasRegistry.registerAlias("LRU", LruCache.class);
  11.     typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
  12.     typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
  13.     typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
  14.     typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
  15.     typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
  16.     typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
  17.     typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
  18.     typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
  19.     typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
  20.     typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
  21.     typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
  22.     typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
  23.     typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
  24.     typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
  25.     languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
  26.     languageRegistry.register(RawLanguageDriver.class);
  27.   }
复制代码
然后在mybatis-config.xml配置文件中配置插件让分页插件生效:
  1. private final Map<String, Class<?>> typeAliases = new HashMap<>();
  2. public TypeAliasRegistry() {
  3.     // 注册别名,最终全部会注册到Map中
  4.     registerAlias("string", String.class);
  5.     registerAlias("byte", Byte.class);
  6.     registerAlias("char", Character.class);
  7.     registerAlias("character", Character.class);
  8.     registerAlias("long", Long.class);
  9.     registerAlias("short", Short.class);
  10.     registerAlias("int", Integer.class);
  11.     registerAlias("integer", Integer.class);
  12.     registerAlias("double", Double.class);
  13.     registerAlias("float", Float.class);
  14.     registerAlias("boolean", Boolean.class);
  15.     registerAlias("byte[]", Byte[].class);
  16.     registerAlias("char[]", Character[].class);
  17.     registerAlias("character[]", Character[].class);
  18.     registerAlias("long[]", Long[].class);
  19.     registerAlias("short[]", Short[].class);
  20.     registerAlias("int[]", Integer[].class);
  21.     registerAlias("integer[]", Integer[].class);
  22.     registerAlias("double[]", Double[].class);
  23.     registerAlias("float[]", Float[].class);
  24.     registerAlias("boolean[]", Boolean[].class);
  25.     registerAlias("_byte", byte.class);
  26.     registerAlias("_char", char.class);
  27.     registerAlias("_character", char.class);
  28.     registerAlias("_long", long.class);
  29.     registerAlias("_short", short.class);
  30.     registerAlias("_int", int.class);
  31.     registerAlias("_integer", int.class);
  32.     registerAlias("_double", double.class);
  33.     registerAlias("_float", float.class);
  34.     registerAlias("_boolean", boolean.class);
  35.     registerAlias("_byte[]", byte[].class);
  36.     registerAlias("_char[]", char[].class);
  37.     registerAlias("_character[]", char[].class);
  38.     registerAlias("_long[]", long[].class);
  39.     registerAlias("_short[]", short[].class);
  40.     registerAlias("_int[]", int[].class);
  41.     registerAlias("_integer[]", int[].class);
  42.     registerAlias("_double[]", double[].class);
  43.     registerAlias("_float[]", float[].class);
  44.     registerAlias("_boolean[]", boolean[].class);
  45.     registerAlias("date", Date.class);
  46.     registerAlias("decimal", BigDecimal.class);
  47.     registerAlias("bigdecimal", BigDecimal.class);
  48.     registerAlias("biginteger", BigInteger.class);
  49.     registerAlias("object", Object.class);
  50.     registerAlias("date[]", Date[].class);
  51.     registerAlias("decimal[]", BigDecimal[].class);
  52.     registerAlias("bigdecimal[]", BigDecimal[].class);
  53.     registerAlias("biginteger[]", BigInteger[].class);
  54.     registerAlias("object[]", Object[].class);
  55.     registerAlias("map", Map.class);
  56.     registerAlias("hashmap", HashMap.class);
  57.     registerAlias("list", List.class);
  58.     registerAlias("arraylist", ArrayList.class);
  59.     registerAlias("collection", Collection.class);
  60.     registerAlias("iterator", Iterator.class);
  61.     registerAlias("ResultSet", ResultSet.class);
  62.   }
复制代码
然后就可以直接使用了:
  1. public Configuration parse() {
  2.     if (parsed) {
  3.         throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  4.     }
  5.     parsed = true;
  6.     // 解析配置,从根的configuration的标签开始
  7.     parseConfiguration(parser.evalNode("/configuration"));
  8.     return configuration;
  9. }
  10. private void parseConfiguration(XNode root) {
  11.     try {
  12.         // issue #117 read properties first
  13.         propertiesElement(root.evalNode("properties"));
  14.         Properties settings = settingsAsProperties(root.evalNode("settings"));
  15.         loadCustomVfs(settings);
  16.         // 加载自定义的日志打印
  17.         loadCustomLogImpl(settings);
  18.         // 解析别名
  19.         typeAliasesElement(root.evalNode("typeAliases"));
  20.         // 添加插件
  21.         pluginElement(root.evalNode("plugins"));
  22.         objectFactoryElement(root.evalNode("objectFactory"));
  23.         objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
  24.         reflectorFactoryElement(root.evalNode("reflectorFactory"));
  25.         // 设置默认的配置
  26.         settingsElement(settings);
  27.         // read it after objectFactory and objectWrapperFactory issue #631
  28.         // 解析环境信息
  29.         environmentsElement(root.evalNode("environments"));
  30.         databaseIdProviderElement(root.evalNode("databaseIdProvider"));
  31.         // 解析类型处理器标签
  32.         typeHandlerElement(root.evalNode("typeHandlers"));
  33.         // 解析mappers标签
  34.         mapperElement(root.evalNode("mappers"));
  35.     } catch (Exception e) {
  36.         throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  37.     }
  38. }
复制代码
日志如下:
[code]Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@14a2528]11:06:43.511 [main] INFO mybatis.plugins.MyPlugin - 对方法进行增强....==>  Preparing: SELECT count(0) FROM user ==> Parameters:

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

守听

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表