美文网首页
Mybatis插件

Mybatis插件

作者: 不知名的蛋挞 | 来源:发表于2019-05-18 20:49 被阅读0次

    Mybatis插件

    Mybatis插件又称拦截器。

    Mybatis采用责任链模式,通过动态代理组织多个插件(拦截器),通过这些插件可以改变Mybatis的默认行为(诸如SQL重写之类的),由于插件会深入到Mybatis的核心,因此在编写自己的插件前最好了解下它的原理,以便写出安全高效的插件。

    MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

    • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
    • ParameterHandler (getParameterObject, setParameters)
    • ResultSetHandler (handleResultSets, handleOutputParameters)
    • StatementHandler (prepare, parameterize, batch, update, query)

    总体概括为:

    • 拦截执行器的方法
    • 拦截参数的处理
    • 拦截结果集的处理
    • 拦截Sql语法构建的处理

    Mybatis四大接口

    竟然Mybatis是对四大接口进行拦截的,那我们要先要知道Mybatis的四大接口对象 Executor, StatementHandler, ResultSetHandler, ParameterHandler。

    上图Mybatis框架的整个执行过程。Mybatis插件能够对这四大对象进行拦截,可以包含到了Mybatis一次查询的所有操作。可见Mybatis的的插件很强大。

    1. Executor是 Mybatis的内部执行器,它负责调用StatementHandler操作数据库,并把结果集通过 ResultSetHandler进行自动映射,另外,他还处理了二级缓存的操作。从这里可以看出,我们也是可以通过插件来实现自定义的二级缓存的。

    2. StatementHandler是Mybatis直接和数据库执行sql脚本的对象。另外它也实现了Mybatis的一级缓存。这里,我们可以使用插件来实现对一级缓存的操作(禁用等等)。

    3. ParameterHandler是Mybatis实现Sql入参设置的对象。插件可以改变我们Sql的参数默认设置。

    4. ResultSetHandler是Mybatis把ResultSet集合映射成POJO的接口对象。我们可以定义插件对Mybatis的结果集自动映射进行修改。

    插件Interceptor

    Mybatis的插件实现要实现Interceptor接口,我们看下这个接口定义的方法。

    public interface Interceptor {   
       //插件运行的代码,它将代替原有的方法
       Object intercept(Invocation invocation) throws Throwable;     
       // 拦截四大接口  
       Object plugin(Object target);    
       // 配置自定义相关属性
       void setProperties(Properties properties);
    }
    

    理解这个接口的定义,先要知道java动态代理机制。plugin接口返回参数target对象(Executor/ParameterHandler/ResultSetHander/StatementHandler)的代理对象。在调用对应对象的接口的时候,可以进行拦截并处理。

    Mybatis四大接口对象创建方法

    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
       //确保ExecutorType不为空(defaultExecutorType有可能为空)
       executorType = executorType == null ? defaultExecutorType : executorType;
       executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
       Executor executor;   if (ExecutorType.BATCH == executorType) {
          executor = new BatchExecutor(this, transaction);
       } else if (ExecutorType.REUSE == executorType) {
          executor = new ReuseExecutor(this, transaction);
       } else {
          executor = new SimpleExecutor(this, transaction);
       }   if (cacheEnabled) {
          executor = new CachingExecutor(executor);
       }
       executor = (Executor) interceptorChain.pluginAll(executor);
       return executor;
    }
    
    public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
       StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
       statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
       return statementHandler;
    }
    
    public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
       ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
       parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
       return parameterHandler;
    }
    
    public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) {
       ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
       resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
       return resultSetHandler;
    }
    

    查看源码可以发现, Mybatis框架在创建好这四大接口对象的实例后,都会调用InterceptorChain.pluginAll()方法。InterceptorChain对象是插件执行链对象,看源码就知道里面维护了Mybatis配置的所有插件(Interceptor)对象。

    // target  --> Executor/ParameterHandler/ResultSetHander/StatementHandler
    public Object pluginAll(Object target) {
       for (Interceptor interceptor : interceptors) {
          target = interceptor.plugin(target);
       }
       return target;
    }
    

    其实就是按顺序执行我们插件的plugin方法,一层一层返回我们原对象(Executor/ParameterHandler/ResultSetHander/StatementHandler)的代理对象。

    插件实现

    下面的MyBatis官网的一个拦截器实例:

    @Intercepts({@Signature(type = Executor.class, method = "update",
            args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
    public class TestInterceptor implements Interceptor {
       public Object intercept(Invocation invocation) throws Throwable {
         Object target = invocation.getTarget(); //被代理对象
         Method method = invocation.getMethod(); //代理方法
         Object[] args = invocation.getArgs(); //方法参数
         // do something ...... 方法拦截前执行代码块
         Object result = invocation.proceed();
         // do something .......方法拦截后执行代码块
         return result;
       }
       public Object plugin(Object target) {
         return Plugin.wrap(target, this);
       }
      public void setProperties(Properties properties) {
      }
    }
    

    Plugin.warp方法会返回四大接口对象的代理对象,会拦截所有的执行方法。这个拦截器拦截Executor接口的update方法(其实也就是SqlSession的新增,删除,修改操作),所有执行executor的update方法都会被该拦截器拦截到。

    Mybatis中利用了注解的方式配置指定拦截哪些方法,只有通过Intercepts注解指定的方法才会执行我们自定义插件的intercept方法。

    下面给出一个拦截的实例。

    //拦截StatementHandler中参数类型为Connection的prepare方法
    @Intercepts({@Signature(type=StatementHandler.class,method="prepare",args={Connection.class})})
    public class PageInterceptor implements Interceptor {
    
        private String test; // 获取xml中配置的属性
    
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            StatementHandler statementHandler = (StatementHandler)invocation.getTarget();
            //通过MetaObject优雅访问对象的属性,这里是访问statementHandler的属性
            MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
            //先拦截到RoutingStatementHandler,里面有个StatementHandler类型的delegate变量,其实现类是BaseStatementHandler
            // 然后获取到BaseStatementHandler的成员变量mappedStatement
            MappedStatement mappedStatement = (MappedStatement)metaObject.getValue("delegate.mappedStatement");
            // 配置文件中SQL语句的ID
            String id = mappedStatement.getId();
            if(id.matches(".+ByPage$")) { //需要拦截的ID(正则匹配)
                BoundSql boundSql = statementHandler.getBoundSql();
                // 原始的SQL语句
                String sql = boundSql.getSql();
                // 此处省略一系列改造代码
                String pageSql = sql + " limit " + page.getDbIndex() + "," + page.getDbNumber();
                metaObject.setValue("delegate.boundSql.sql", pageSql);
            }
            return invocation.proceed();
        }
    
        @Override
        public Object plugin(Object target) {
            // 当目标类是StatementHandler类型时,才包装目标类,否者直接返回目标本身,减少目标被代理的次数
            if (target instanceof StatementHandler) {
                return Plugin.wrap(target, this);
            } else {
                return target;
            }
        }
    
        @Override
        public void setProperties(Properties properties) {
            this.test = properties.getProperty("test");
        }
    }
    

    MetaObject是Mybatis提供的一个用于方便、优雅访问对象属性的对象,通过它可以简化代码、不需要try/catch各种reflect异常,同时它支持对JavaBean、Collection、Map三种类型对象的操作。

    MetaObject 的构造器是私有的,获取MetaObject对象需要使用静态方法MetaObject.forObject,并且需要指定ObjectFactory,ObjectWrapperFactory,ReflectorFactory(3.3.0之前不需要)。

    相关文章

      网友评论

          本文标题:Mybatis插件

          本文链接:https://www.haomeiwen.com/subject/zhxtzqtx.html