Mybatis应用篇(二): 自定义插件

1,198 阅读3分钟

前言

大部分的开源框架都提供插件或其他形式的拓展点(比如拦截器),供开发者自行拓展。一方面增加了框架的灵活性,另一方面也方便开发者根据自己实际需要进行自定义拓展。
Mybatis也提供用户自定义插件的机制,比如我们常用地分页插件,还比如我们实际业务中需要使用的监控、分数据源等功能。今天我们就介绍一下如何自定义插件。


mybatis执行sql的核心组件

1、Executor:mybatis的内部执行器,作为调度核心负责调用StatementHandler操作数据库,并把结果集通过ResultSetHandler进行自动映射

2、StatementHandler: 封装了JDBC Statement操作,是sql语法的构建器,负责和数据库进行交互执行sql语句

3、ParameterHandler:作为处理sql参数设置的对象,主要实现读取参数和对PreparedStatement的参数进行赋值

4、ResultSetHandler:处理Statement执行完成后返回结果集的接口对象,mybatis通过它把ResultSet集合映射成实体对象

因此,针对上面4个组件中具体的方法进行拦截,我们就可以对具体地执行对象进行处理(打印日志,修改参数等)。

自定义插件基本规范

  • 实现 org.apache.ibatis.plugin.Interceptor

    plugin(Object target) 将执行对象包装一下返回即可

    intercept(Invocation invocation) 建议参考各拦截点的实现类如何处理的 ,比如拦截 StatementHandler 就参考SimpleStatementHandler

  • 添加注解 @Intercepts

  • 生成插件bean到spring容器中进行管理

  • 将插件对象注入到 sqlSessionFactory 中

//自动将 Interceptor 接口的全部实现类的实例注入进来
@Autowired
private Interceptor[] interceptors;

部分代码片段
//将全部拦截器设置进来
factoryBean.setPlugins(interceptors);

4类 插件示例

以Executor 为拦截点

对 Executor 的 2个 query 方法进行拦截

@Intercepts({@Signature(
        type = Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
), @Signature(
        type = Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}
)})
@Slf4j
@Component
public class ExecutorPlugin implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement)args[0];
        Object parameter = args[1];
        RowBounds rowBounds = (RowBounds)args[2];
        ResultHandler resultHandler = (ResultHandler)args[3];
        Executor executor = (Executor)invocation.getTarget();
        CacheKey cacheKey;
        BoundSql boundSql;
        if (args.length == 4) {
            boundSql = ms.getBoundSql(parameter);
            cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
        } else {
            cacheKey = (CacheKey)args[4];
            boundSql = (BoundSql)args[5];
        }
        log.info("ExecutorPlugin 插件执行...");
        return executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
    }

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

    @Override
    public void setProperties(Properties properties) {

    }

关于 query 方法如何拦截, com.github.pagehelper.QueryInterceptor 做了一个示例 ,大家可以参考下

以 StatementHandler 为拦截点

@Intercepts({@Signature(
        type = StatementHandler.class,
        method = "query",
        args = {Statement.class, ResultHandler.class})
})
@Slf4j
@Component
public class StatementHandlerPlugin implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        Statement statement = (Statement)args[0];
        ResultHandler resultHandler = (ResultHandler)args[1];
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        log.info("StatementHandlerPlugin 插件执行...");
        return statementHandler.query(statement, resultHandler);
    }

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

    @Override
    public void setProperties(Properties properties) {

    }
}

以 ParameterHandler 为拦截点

@Intercepts({@Signature(
        type = ParameterHandler.class,
        method = "setParameters",
        args = {PreparedStatement.class})
})
@Slf4j
@Component
public class ParameterHandlerPlugin implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        PreparedStatement ps = (PreparedStatement)args[0];
        ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
        parameterHandler.setParameters(ps);
        log.info("ParameterHandlerPlugin 插件执行...");
        return null;
    }

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

    @Override
    public void setProperties(Properties properties) {

    }
}

以ResultSetHandler为拦截点

@Intercepts({@Signature(
        type = ResultSetHandler.class,
        method = "handleResultSets",
        args = {Statement.class})
})
@Slf4j
@Component
public class ResultSetHandlerPlugin implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        PreparedStatement ps = (PreparedStatement)args[0];
        ResultSetHandler resultSetHandler = (ResultSetHandler) invocation.getTarget();
        ps.execute();
        log.info("ResultSetHandler 插件执行...");
        return resultSetHandler.handleResultSets(ps);
    }

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

    @Override
    public void setProperties(Properties properties) {

    }
}

总结

以上只是简单地介绍了各个拦截点的插件实现方式,具体实现情况要根据自己的业务场景进行判断应该在哪个拦截点进行处理比较好。
另外, setProperties 方法基本上是空着的,大家在实践中如果有一些参数设置方面的,可以在这个方法里进行。