zoukankan      html  css  js  c++  java
  • Mybatis_总结_06_用_插件开发

    一、前言

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

    二、会被拦截的接口

    Mybatis 允许在映射语句执行过程中的某一点进行拦截调用。

    默认情况下,Mybatis允许使用插件来拦截的接口和方法包括以下几个:

    序号 接口 方法 描述
    1 Executor update、query、flushStatements、commit、rollback、getTransaction、close、isClosed

    拦截执行器的方法

    2 ParameterHandler getParameterObject、setParameters  

    拦截参数的处理

    3 ResultSetHandler handleResultSets、handleCursorResultSets、handleOutputParameters  

    拦截结果集的处理

    4 StatementHandler prepare、parameterize、batch、update、query  

    拦截Sql语法构建的处理

    Mybatis是通过动态代理的方式实现拦截的,阅读此篇文章需要先对Java的动态代理机制有所了解。可以参考博客《彻底理解java动态代理》

    三、Mybatis四大接口

     

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

     

    图1-1   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);
    }

    这个接口只声明了三个方法。

    1.setProperties

    在Mybatis的配置文件中配置插件时,可通过此方法来传递参数给插件。

    如,在mybatis-config.xml中,一般情况下,拦截器的配置如下:

    <plugins>
        <!-- 1.interceptor属性为拦截器实现类的全类名  -->
        <plugin  interceptor="tk.mybatis.simple.plugin.XXXInterceptor">
            <!-- 2.通过property标签来配置参数,配置的参数在拦截器初始化时会通过setProperties方法传递给拦截器。 在拦截器中可以很方便的通过Properties取得配置的参数值  -->
             <property  name="prop1"  value="value1" />
             <property  name="prop2"  value="value2" />
        </plugin>
    </plugins>

    2.plugin

    此方法的参数target就是拦截器要拦截的对象,该方法会在创建被拦截的接口实现类时被调用 ???

    该方法的实现很简单,只需要调用Mybatis提供的Plugin(org.apache.ibatis.plugin.Plugin)类的wrap静态方法就可以通过Java的动态代理拦截目标对象。

     这个方法的通常实现代码如下:

    @Override
    public Object plugin(Object target) {
       return Plugin.wrap(target, this);
    }

     Plugin.wrap方法会自动判断拦截器的签名和被拦截对象的接口是否匹配,只有匹配的情况下才会使用动态代理拦截目标对象,因此在上面的实现方法中不必做额外的判断逻辑。

     来看一个稍微复杂一点的例子。

        @Override
        public Object plugin(Object target) {
            if (target instanceof StatementHandler) {
                return Plugin.wrap(target, this);
            }
            if (target instanceof Executor) {
                final Executor e = (Executor) target;
                Executor executor = new Executor() {
                    public int update(MappedStatement ms, Object parameter) throws SQLException {
                        return e.update(ms, parameter);
                    }
    
                    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds,
                            ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException {
                        return e.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
                    }
    
                    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds,
                            ResultHandler resultHandler) throws SQLException {
                        BoundSql boundSql = ms.getBoundSql(parameter);
                        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
                        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
                    }
    
                    public List<BatchResult> flushStatements() throws SQLException {
                        return e.flushStatements();
                    }
    
                    public void commit(boolean required) throws SQLException {
                        e.commit(required);
                    }
    
                    public void rollback(boolean required) throws SQLException {
                        e.rollback(required);
                    }
    
                    public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds,
                            BoundSql boundSql) {
                        IRequest request = RequestHelper.getCurrentRequest(true);
                        boundSql.setAdditionalParameter("request", request);
                        return e.createCacheKey(ms, parameterObject, rowBounds, boundSql);
                    }
    
                    public boolean isCached(MappedStatement ms, CacheKey key) {
                        return e.isCached(ms, key);
                    }
    
                    public void clearLocalCache() {
                        e.clearLocalCache();
                    }
    
                    public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key,
                            Class<?> targetType) {
                        e.deferLoad(ms, resultObject, property, key, targetType);
                    }
    
                    public Transaction getTransaction() {
                        return e.getTransaction();
                    }
    
                    public void close(boolean forceRollback) {
                        e.close(forceRollback);
                    }
    
                    public boolean isClosed() {
                        return e.isClosed();
                    }
    
                    public void setExecutorWrapper(Executor executor) {
                        e.setExecutorWrapper(executor);
                    }
                };
    
                return executor;
                // return Plugin.wrap(executor, this);
            }
            return target;
        }
    View Code

     上述代码中对匹配条件做了进一步的细化

    3.intercept

    此方法是Mybatis运行时要执行的拦截方法,

    通过该方法的参数invocation可以得到很多有用的信息。

    @Override
    public Object intercept(Invocation invocation) throws Throwable{
       Object target = invocation.getTarget();
       Method method = invocation.getMethod();
       Object[] args = invocation.getArgs();
       Object result = invocation.proceed();
    
       return result;
    }

    通过调用 invocation.proceed();可以执行被拦截对象真正的方法。proceed()方法实际上执行了method.invoke(target,args)方法、

    4.多个插件的调用顺序

    当配置多个拦截器时,Mybatis会遍历所有拦截器,按顺序执行拦截器的plugin方法,被拦截的对象就会被层层代理

    在执行拦截对象的方法时,会一层一层地调用拦截器,拦截器通过invocation.proceed()调用下一层的方法,直到真正的方法被执行。方法执行的结果会从最里面开始向外一层层返回,所以如果存在按顺序配置的ABC三个签名相同的拦截器,Mybatis会按照 C->B->A-> target.proceed() -> A->B->C 

     五、拦截器注解

     除了需要实现拦截器接口外,还需要给实现类配置以下的拦截器注解:

    (1)@Intercepts

    (2)@Signature

    使用这两个注解可以用来配置拦截器要拦截的接口。

    1.注解说明

    以拦截 ResultSetHandler 接口的 handleResultSets 方法为例,配置签名如下。

    @intercepts({
        @Signature(
            type   = ResultSetHandler.class,
            method = "handleResultSets",
            args   = {Statement.class}
        )
    })
    public class ResultSetInterceptor implements Interceptor

    @Signature 注解主要包含以下三个属性:

    (1)type      :设置拦截的接口,可选值是前面提到的四个接口

    (2)method :设置拦截接口中的方法名,可选值是前面4个接口对应的方法,需要和接口匹配。

    (3)args      :设置拦截器的参数类型

    六、参考资料

    1.Mybatis插件原理

  • 相关阅读:
    华为上机练习题--求两个数组的总和
    C++设计模式之状态模式(四)
    深入理解java嵌套类和内部类
    c++实现精确计时
    Linux-中断和中断处理
    使用C#对MongoDB中的数据进行查询,改动等操作
    淘特房产CMS系统 7.5
    sass03 变量、样式导入
    sass02
    sass01
  • 原文地址:https://www.cnblogs.com/shirui/p/9610911.html
Copyright © 2011-2022 走看看