揭秘MyBatis插件:深入理解与实战运用

目录

一、插件介绍

二、自定义插件实现

三、实现原理


一、插件介绍

        开源框架都注重自身的扩展性,为用户提供了扩展机制,这样具有很高的灵活性,同时也能根据自身的业务定制实现一些额外的功能。

        MyBatis 作为优秀的 ORM 框架,自然也考虑到了扩展性,这样使其拥有强大的灵活性。Mybatis 中虽然提供的叫插件,但是实际上是通过拦截器实现的,通过拦截某些方法的调用,植入拦截器的逻辑,实现插件功能。

        MyBatis 在执行SQL时涉及到了四个组件,分别为 Executor、StatementHandler、ParameterHandler、ResultSetHandler,其功能如下:

  • Executor:SQL 执行器,MyBatis 中对数据库的所有增删改查都是通过 Executor 实现的。
  • StatementHandler:封装了对 JDBC Statement 对象的操作,如参数设置、编译 SQL、处理结果集等,根据不同的 SQL 类型,使用不同的子类实现。
  • ParameterHandler:当使用预编译时,负责设置参数值到 SQL 语句的占位符上,负责处理动态 SQL 中的参数替换。
  • ResultSetHandler:负责将从数据库查询结果转换为 Java 对象,包括单个对象、集合、自定义复杂结果类型等。根据 ResultMap 配置来映射结果集中的列到 Java 对象的属性上。

        MyBatis 支持用插件对这四大组件进行拦截,用来增强核心组件的功能,底层是通过动态代理技术实现的。

        MyBatis 插件的应用非常广泛,主要用于增强或定制化 MyBatis 的功能,而不必直接修改源码,下面是一些常用的场景。

  • 日志记录与性能监控:插件可以拦截执行器(Executor)、语句处理器(StatementHandler)等组件的方法,记录 SQL 语句、执行时间、参数信息等,用于调试、性能分析或审计目的。
  • 动态 SQL 优化:插件可以分析和优化动态生成的 SQL 语句,比如去除无效的条件、合并重复的子查询等,提高查询效率。
  • 自动分页:通过插件来实现分页查询的功能。
  • 数据脱敏:对敏感数据(如身份证号、手机号登)进行脱敏处理,确保在查询结果中返回的事加密、脱敏后的数据
  • 数据校验:在执行 INSERT、UPDATE 操作前,插件可以对即将写入数据库的参数进行合法性校验,如格式检查、唯一性验证、业务规则检查等,保证数据完整性。

        下面就来实现一个记录 SQL 执行时间的插件,以实现对慢SQL的监控。

二、自定义插件实现

         这里假设你的项目中已经集成了 MyBatis,直接编写插件相关类实现

@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 {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        // 获取 StatementHandler
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        // 获取 StatementHandler 的包装类
        MetaObject metaObjectHandler = SystemMetaObject.forObject(statementHandler);
        // 获取查询接口映射的相关信息
        MappedStatement mappedStatement = (MappedStatement) metaObjectHandler.getValue("delegate.mappedStatement");
        // 获取请求时的参数
        Object parameterObject = statementHandler.getParameterHandler().getParameterObject();
        // 获取SQL
        BoundSql boundSql = mappedStatement.getBoundSql(parameterObject);
        // showSql 方法中将参数值拼接在SQL语句中,具体实现省略
        String sql = showSql(mappedStatement.getConfiguration(), boundSql);
        // 执行
        Object result = invocation.proceed();
        stopWatch.stop();
        System.out.println("当前执行SQL为:" + sql + ", 执行时间为:" + stopWatch.getTotalTimeMillis() + "ms");
        return result;
    }

    /**
     * 获取对象代理
     * @param target
     * @return
     */
    @Override
    public Object plugin(Object target) {
        return (target instanceof StatementHandler) ? Plugin.wrap(target, this) : target;
    }

    @Override
    public void setProperties(Properties properties) {

    }

    private String showSql(Configuration configuration, BoundSql boundSql) {
        // 忽略
    }

   
}

        在Mybatis的配置类中引入自定义插件


@MapperScan(basePackages = "com.test.mybatis.mapper", sqlSessionTemplateRef = "sqlSessionTemplate")
@Configuration
public class MybatisConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/**.xml"));
        bean.setVfs(SpringBootVFS.class);
        // 添加拦截器插件
        bean.setPlugins(new Interceptor[]{myBatisSqlInterceptor()});
        // 实体类位置
        bean.setTypeAliasesPackage("com.test.mybatis.model.User");
        return bean.getObject();
    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    @Bean
    public DataSourceTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource);
    }

    // 设置自定义插件
    private Interceptor myBatisSqlInterceptor() {
        SqlExecutionTimeInterceptor sqlExecutionTimeInterceptor = new SqlExecutionTimeInterceptor();
        sqlExecutionTimeInterceptor.setProperties(new Properties());
        return sqlExecutionTimeInterceptor;
    }
}

        执行SQL语句时,就能将SQL的执行时间打印出来,如果超过了慢查询设置的值,还可以发出来预警。打印的具体内容如下:

当前执行SQL为:select id, city, `name`, age, addr from user where id = 1, 执行时间为:3ms

三、实现原理

         MyBatis 插件的核心是 Interceptor 接口,它只有一个方法 Object intercept(Invocation invocation)。插件需要实现此接口以实现对目标方法的拦截。Invocation 对象封装了被拦截方法的信息,包括方法对象(Method)、目标对象(Target)、参数列表(Arguments)以及代理对象(Proxy)。通过这些信息,插件可以在拦截时获取到需要的数据并执行相应的操作。

        在插件类上使用 @Intercepts 注解,指定要拦截的方法签名。具体如代码实现。

        Mybatis 使用动态代理技术,在运行时为各个核心接口(如Executor、StatementHandler等)生成代理对象。当调用代理对象的方法时,实际会调用InvocationHandler 的 invoke() 方法。

        在方法运行时会调用 plugin() 方法,生成代理对象,代码如下:

        通过这种设计使得开发者能够灵活地对 MyBatis 的核心行为进行扩展和定制,而不必修改 MyBatis 源码,极大地提高了框架的灵活性和可扩展性。

往期经典推荐

 Elasticsearch的可靠性保障-CSDN博客

如何熟悉一个陌生的业务系统-CSDN博客

走进 Mybatis 内核世界:理解原理,释放更多生产力-CSDN博客

深入浅出 TiDB MVCC:揭秘分布式数据库中的多版本并发控制-CSDN博客

直击Redis集群痛点:数据倾斜优化实战,打造高效分布式缓存架构_redis集群实现的集群如何解决数据倾斜-CSDN博客

  • 35
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

超越不平凡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值