Mybatis 插件机制

MyBatis 拦截器(Interceptor)是 MyBatis 提供的一个强大的扩展机制,允许开发者在 SQL 执行过程中插入自定义逻辑。通过拦截器,你可以在 SQL 执行前后、参数处理、结果集处理等阶段进行干预,从而实现诸如 SQL 日志记录、分页、权限控制、性能监控等功能。

没有插件的运行图

图片Base64编码

有插件的运行图

图片Base64编码

1. 拦截器的工作原理

MyBatis 拦截器基于 Java 的动态代理机制。MyBatis 的核心组件(如 ExecutorStatementHandlerParameterHandlerResultSetHandler 等)在执行时会被代理,拦截器可以在这些组件的方法执行前后插入自定义逻辑。

2. 拦截器的核心接口

MyBatis 拦截器的核心接口是 org.apache.ibatis.plugin.Interceptor,它定义了以下方法:

  • intercept(Invocation invocation):这是拦截器的核心方法,Invocation 对象封装了被拦截的方法及其参数。你可以在这个方法中编写自定义逻辑,并决定是否继续执行被拦截的方法。

  • plugin(Object target):这个方法用于将拦截器应用到目标对象上。通常可以使用 Plugin.wrap(target, this) 来生成代理对象。

  • setProperties(Properties properties):这个方法用于配置拦截器的属性,可以通过 MyBatis 配置文件或注解来传递参数。

3. 实现一个简单的拦截器

下面是一个简单的 MyBatis 拦截器示例,用于记录 SQL 执行的时间:

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.mapping.BoundSql;

import java.sql.Connection;
import java.util.Properties;

@Intercepts({
    @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class SqlExecutionTimeInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        // 只拦截查询sql
        // if (!mappedStatement.getSqlCommandType().equals(SqlCommandType.SELECT)) {
        //     return invocation.proceed();
        // }

        long startTime = System.currentTimeMillis();
        Object result = invocation.proceed(); // 继续执行被拦截的方法
        long endTime = System.currentTimeMillis();
        
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        BoundSql boundSql = statementHandler.getBoundSql();
        String sql = boundSql.getSql();

        // 如果要修改sql(原始sql追加 id 大于 0)
        // String newSql = sql + " and  id > 0";
        // Field field = boundSql.getClass().getDeclaredField("sql");
        // field.setAccessible(true);
        // field.set(boundSql, newSql);
        
        System.out.println("SQL: " + sql);
        System.out.println("Execution Time: " + (endTime - startTime) + "ms");
        
        return result;
    }

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

    @Override
    public void setProperties(Properties properties) {
        // 可以通过 properties 配置拦截器的参数
    }
}

4. 配置拦截器

在 MyBatis 全局配置文件中,可以通过 <plugins> 标签来注册拦截器:

<plugins>
    <plugin interceptor="com.example.SqlExecutionTimeInterceptor">
        <!-- 可以在这里配置拦截器的属性 -->
        <property name="someProperty" value="someValue"/>
    </plugin>
</plugins>

springboot 通过配置组件来完成配置

@Configuration
public class MybatisPlugin {

    @Resource
    private SqlSessionFactory sqlSessionFactory;

    @PostConstruct
    public void addInterceptor() throws NoSuchFieldException, IllegalAccessException {
        sqlSessionFactory.getConfiguration().addInterceptor(new SqlExecutionTimeInterceptor());
    }

}

5. 拦截器的应用场景

  • SQL 日志记录:记录执行的 SQL 语句及其执行时间,便于调试和性能分析。
  • 分页:通过拦截器自动处理分页逻辑,无需在每个查询中手动编写分页代码。
  • 权限控制:在 SQL 执行前检查用户权限,防止未授权的数据访问。
  • 性能监控:监控 SQL 执行时间,识别慢查询。
  • SQL 改写:在 SQL 执行前动态修改 SQL 语句,例如添加租户 ID 等。

6. 拦截器的执行顺序

如果有多个拦截器,MyBatis 会按照它们在配置文件中定义的顺序依次执行。每个拦截器的 plugin 方法会生成一个代理对象,后续的拦截器会继续对这个代理对象进行包装。

7. 注意事项

  • 性能影响:拦截器会增加额外的开销,尤其是在高并发场景下,频繁的代理和拦截操作可能会影响性能。
  • 谨慎使用:拦截器是一个强大的工具,但过度使用可能会导致代码难以维护和理解。建议只在必要时使用拦截器。
  • 线程安全:拦截器本身是无状态的,但如果拦截器中使用了共享资源,需要注意线程安全问题。

8. 总结

MyBatis 拦截器提供了一种灵活的方式来扩展 MyBatis 的功能。通过拦截器,你可以在 SQL 执行的各个阶段插入自定义逻辑,从而实现各种高级功能。然而,拦截器也需要谨慎使用,避免对性能产生负面影响。

posted @ 2023-07-28 16:22  CyrusHuang  阅读(109)  评论(0)    收藏  举报