Mybatis 插件
Mybatis插件主要是通过JDK动态代理实现的,插件可以针对接口中的方法进行代理增强,在Mybatis中比较重要的接口如下:
- Executor :sql执行器,包含多个实现类,比如SimpleExecutor
- StatementHander: sql语句处理器,用于将sql语句与Statement的映射,实现类有:PrepareStatementHandler、SimpleStatementHandler、CallBackStatementHandler
- ParameterHandler:用于参数处理,将传入的参数一一的解析并将类型解析出来,会用到TypeHandler,最终这些数据会用于StatementHandler进行数据的映射,比如?对应的值的映射
- ResultSetHandler:结果值的处理器,用于数据在查询出来之后,将数据通过ResultSet把数据映射给返回值类型的类上,通过反射(内省)处理映射数据

Mybatis插件的使用
Mybatis插件使用通过@Intercepts注解进行接口的绑定,如下定义一个插件类- /**
- * @author <a target="_blank" href="https://www.cnblogs.com/2360564660@qq.com">redwinter</a>
- * @since 1.0
- **/
- @Intercepts({@Signature(
- type = StatementHandler.class,
- method = "prepare",
- args = {Connection.class, Integer.class}
- )})
- @Slf4j
- public class MyPlugin implements Interceptor {
- @Override
- public Object intercept(Invocation invocation) throws Throwable {
- log.info("对方法进行增强....");
- return invocation.proceed();
- }
- @Override
- public Object plugin(Object target) {
- return Plugin.wrap(target, this);
- }
- @Override
- public void setProperties(Properties properties) {
- log.info("获取属性值:{}", properties);
- }
- }
复制代码 然后需要将定义的插件配置mybatis的配置文件中:- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
- <configuration>
-
- <properties resource="db.properties"/>
-
- <settings>
-
- <setting name="logImpl" value="STDOUT_LOGGING" />
-
- <setting name="cacheEnabled" value="true"/>
- </settings>
- <typeAliases>
- <package name="com.redwinter.study.mybatis.model"/>
- </typeAliases>
-
- <plugins>
- <plugin interceptor="mybatis.plugins.MyPlugin">
- <property name="redwinter" value="冬玲"/>
- </plugin>
- <plugin interceptor="com.github.pagehelper.PageInterceptor">
-
- </plugin>
- </plugins>
-
- <environments default="development">
- <environment id="development">
- <transactionManager type="JDBC" />
-
- <dataSource type="POOLED">
-
- <property name="driver" value="${driver}" />
- <property name="url" value="${url}" />
- <property name="username" value="${name}" />
- <property name="password" value="${password}" />
- </dataSource>
- </environment>
- </environments>
- <mappers>
- <package name="mybatis.mapper"/>
- </mappers>
- </configuration>
复制代码 这样就可以生效了,当我们执行数据查询的时候,只要是执行了StatementHandler#prepare方法,那么都会执行到自定的逻辑增强
日志如下:- Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@1386958]
- 16:02:38.260 [main] INFO mybatis.plugins.MyPlugin - 对方法进行增强....
- ==> Preparing: update user set name = ?, age = ? where id = ?
- ==> Parameters: 李四(String), 19(Integer), 1(Integer)
- <== Updates: 1
- Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@1386958]
- Cache Hit Ratio [mybatis.mapper.UserMapper]: 0.5
- 16:02:38.303 [main] INFO mybatis.plugins.MyPlugin - 对方法进行增强....
- ==> Preparing: select * from user where id = ?
- ==> Parameters: 1(Integer)
- <== Columns: id, age, name
- <== Row: 1, 19, 李四
- <== Total: 1
- false
复制代码 这样的话就完成了拦截器插件的代理对象的创建,这里创建出来的代理对象就是StatementHandler,在前面自定义的插件,配置的是拦截StatementHandler#prepare方法,那么在哪里执行的呢?
回到Executor接口实现类SimpleExecutor了中doQuery方法,这个方法中会去创建一个预编译SQL处理器,执行prepareStatement方法:- public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
- try {
- // 创建一个解析xml的构建器,构造函数中会创建一个Configuration类
- XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
- // 解析xml配置
- return build(parser.parse());
- } catch (Exception e) {
- throw ExceptionFactory.wrapException("Error building SqlSession.", e);
- } finally {
- ErrorContext.instance().reset();
- try {
- if (inputStream != null) {
- inputStream.close();
- }
- } catch (IOException e) {
- // Intentionally ignore. Prefer previous error.
- }
- }
- }
复制代码 这里的话就会调用prepare方法,这个方法就是自定义插件配置需要拦截的方法,由于这个handler是一个代理对象,我们都知道只要是代理对象,只要执行代理对象的任何方法都会去执行InvoketionHandler接口的invoke方法,当执行到这个方法的时候就会调用到我们自定义的插件类中intercept方法:- private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
- super(new Configuration());
- ErrorContext.instance().resource("SQL Mapper Configuration");
- this.configuration.setVariables(props);
- this.parsed = false;
- this.environment = environment;
- this.parser = parser;
- }
复制代码 所以只要我们执行了sql查询,那么都会通过JDK动态代理创建的代理对象去执行到这个增强方法。
插件的扩展
在Mybatis中有个分页的插件叫PageHelper,这个插件就是使用了Mybatis插件机制完成的,当然还有比如早期的TkMapper插件。接下来分析一下PageHelper是如何实现分页机制的。
引入依赖:- public Configuration() {
- // 添加别名
- typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
- typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
- typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
- typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
- typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
- typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
- typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
- typeAliasRegistry.registerAlias("LRU", LruCache.class);
- typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
- typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
- typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
- typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
- typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
- typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
- typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
- typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
- typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
- typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
- typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
- typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
- typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
- typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
- languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
- languageRegistry.register(RawLanguageDriver.class);
- }
复制代码 然后在mybatis-config.xml配置文件中配置插件让分页插件生效:- private final Map<String, Class<?>> typeAliases = new HashMap<>();
- public TypeAliasRegistry() {
- // 注册别名,最终全部会注册到Map中
- registerAlias("string", String.class);
- registerAlias("byte", Byte.class);
- registerAlias("char", Character.class);
- registerAlias("character", Character.class);
- registerAlias("long", Long.class);
- registerAlias("short", Short.class);
- registerAlias("int", Integer.class);
- registerAlias("integer", Integer.class);
- registerAlias("double", Double.class);
- registerAlias("float", Float.class);
- registerAlias("boolean", Boolean.class);
- registerAlias("byte[]", Byte[].class);
- registerAlias("char[]", Character[].class);
- registerAlias("character[]", Character[].class);
- registerAlias("long[]", Long[].class);
- registerAlias("short[]", Short[].class);
- registerAlias("int[]", Integer[].class);
- registerAlias("integer[]", Integer[].class);
- registerAlias("double[]", Double[].class);
- registerAlias("float[]", Float[].class);
- registerAlias("boolean[]", Boolean[].class);
- registerAlias("_byte", byte.class);
- registerAlias("_char", char.class);
- registerAlias("_character", char.class);
- registerAlias("_long", long.class);
- registerAlias("_short", short.class);
- registerAlias("_int", int.class);
- registerAlias("_integer", int.class);
- registerAlias("_double", double.class);
- registerAlias("_float", float.class);
- registerAlias("_boolean", boolean.class);
- registerAlias("_byte[]", byte[].class);
- registerAlias("_char[]", char[].class);
- registerAlias("_character[]", char[].class);
- registerAlias("_long[]", long[].class);
- registerAlias("_short[]", short[].class);
- registerAlias("_int[]", int[].class);
- registerAlias("_integer[]", int[].class);
- registerAlias("_double[]", double[].class);
- registerAlias("_float[]", float[].class);
- registerAlias("_boolean[]", boolean[].class);
- registerAlias("date", Date.class);
- registerAlias("decimal", BigDecimal.class);
- registerAlias("bigdecimal", BigDecimal.class);
- registerAlias("biginteger", BigInteger.class);
- registerAlias("object", Object.class);
- registerAlias("date[]", Date[].class);
- registerAlias("decimal[]", BigDecimal[].class);
- registerAlias("bigdecimal[]", BigDecimal[].class);
- registerAlias("biginteger[]", BigInteger[].class);
- registerAlias("object[]", Object[].class);
- registerAlias("map", Map.class);
- registerAlias("hashmap", HashMap.class);
- registerAlias("list", List.class);
- registerAlias("arraylist", ArrayList.class);
- registerAlias("collection", Collection.class);
- registerAlias("iterator", Iterator.class);
- registerAlias("ResultSet", ResultSet.class);
- }
复制代码 然后就可以直接使用了:- public Configuration parse() {
- if (parsed) {
- throw new BuilderException("Each XMLConfigBuilder can only be used once.");
- }
- parsed = true;
- // 解析配置,从根的configuration的标签开始
- parseConfiguration(parser.evalNode("/configuration"));
- return configuration;
- }
- private void parseConfiguration(XNode root) {
- try {
- // issue #117 read properties first
- propertiesElement(root.evalNode("properties"));
- Properties settings = settingsAsProperties(root.evalNode("settings"));
- loadCustomVfs(settings);
- // 加载自定义的日志打印
- loadCustomLogImpl(settings);
- // 解析别名
- typeAliasesElement(root.evalNode("typeAliases"));
- // 添加插件
- pluginElement(root.evalNode("plugins"));
- objectFactoryElement(root.evalNode("objectFactory"));
- objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
- reflectorFactoryElement(root.evalNode("reflectorFactory"));
- // 设置默认的配置
- settingsElement(settings);
- // read it after objectFactory and objectWrapperFactory issue #631
- // 解析环境信息
- environmentsElement(root.evalNode("environments"));
- databaseIdProviderElement(root.evalNode("databaseIdProvider"));
- // 解析类型处理器标签
- typeHandlerElement(root.evalNode("typeHandlers"));
- // 解析mappers标签
- mapperElement(root.evalNode("mappers"));
- } catch (Exception e) {
- throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
- }
- }
复制代码 日志如下:
[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: |