一、前言

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。

 Mybatis_总结_06_用_插件开发

图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

相关文章:

  • 2021-09-04
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2021-12-27
猜你喜欢
  • 2022-12-23
  • 2021-08-08
  • 2021-07-21
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2021-07-30
相关资源
相似解决方案