大白话讲解Mybatis的plugin(Interceptor)的使用
mybatis提供了一个入口,可以让你在语句执行过程中的某一点进行拦截调用。官方称之为插件plugin,但是在使用的时候需要实现Interceptor接口,默认情况下,MyBatis 允许使用插件来拦截的方法调用包括以下四个对象的方法:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
以上内容在官网包括网上一搜一大把,但是用的时候,应该怎么选择,什么时候用哪种,怎么入手呢?
我一开始想用的时候,也不知道什么时候拦截哪种对象,后来我就写了一个简单的demo,大家在用mybatis的时候,无非就是crud操作,那么我就提供四个plugin,分别来拦截Executor、ParameterHandler、ResultSetHandler、StatementHandler ;然后提供了一个controller暴露了五个接口分别是getUserInfo、listUserInfo、addUser、updateUser、deleteUser,来看下都走了那几个plugin(demo我会上传到码云上,项目架构是springboot+mybatis+mybatis-plus,数据库我用的是postgresql-14),我认为这五个接口涵盖了我们在开发中90%的场景,根据打印的日志得到的结论是:
- 两种查询、新增、修改、删除五个方法都会经过StatementHandler、ParameterHandler
- 两种查询(单个查询、列表查询)都会经过Executor、StatementHandler、ParameterHandler、ResultSetHandler
所以根据上面的结论,我们就可以来确定我们在开发中用哪种plugin,参考场景如下:
- 如果想改入参,比如postgresql据库字段值大小写敏感,那么我可以在ParameterHandler里面获取到入参,然后toUpperCase();
- 如果想改sql语句,比如改postgresql的schema,那么我可以在StatementHandler(prepare)里面获取到connection修改;若是查询场景也可以在Executor的query方法中获取connection修改;
- 如果想对数据进行脱敏处理,比如查询场景下的,查出的结果中身份证显示前4位后4位中间***填充,那么我们可以在ResultSetHandler的进行脱敏处理。
下面结合代码举两个场景的例子:
场景一:对查询结果数据脱敏处理,首先定义了一个XfactorResultSetHandlerInterceptor,代码如下:
package 域名域名per; import 域名域名d; import 域名ement; import 域名; import 域名域名域名ngUtils; import 域名域名域名ltSetHandler; import 域名域名rceptor; import 域名域名rcepts; import 域名域名cation; import 域名域名ature; import 域名域名Object; import 域名域名emMetaObject; import 域名域名j; @Slf4j @Intercepts({ @Signature(type= 域名s,method = "handleResultSets",args = {域名s}) }) public class XfactorResultSetHandlerInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { 域名("===ResultSetHandler==="); Object resultSet = 域名eed(); List resultList = (List)resultSet; for(Object item : resultList) { Class<?> sourceClass = 域名lass(); MetaObject metaObject = 域名bject(item); Field[] fields = 域名eclaredFields(); for(Field field : fields) { if(域名ls(域名ame(), "password")) { 域名alue(域名ame(), "******"); } } } return resultSet; } }
plugin定义好以后,要想让插件起作用,需要把插件加入到MybatisSqlSessionFactoryBean中,代码如下(见标黄的部分)
package 域名域名ig; import 域名Source; import 域名域名essionFactory; import 域名域名erScan; import 域名域名域名wired; import 域名.域名SourceProperties; import 域名.域名SourceBuilder; import 域名域名; import 域名域名iguration; import 域名.域名MatchingResourcePatternResolver; import 域名域名pe; import 域名域名域名tisPlusInterceptor; import 域名域名域名nationInnerInterceptor; import 域名域名域名tisSqlSessionFactoryBean; import 域名域名域名torRuntimeException; import 域名域名域名torExecutorInterceptor; import 域名域名域名torParameterHandlerInterceptor; import 域名域名域名torResultSetHandlerInterceptor; import 域名域名域名torStatementHandlerInterceptor; import 域名域名riDataSource; import 域名域名j; @Slf4j @Configuration @MapperScan("域名域名") public class DataSourceConfig { @Autowired private DataSourceProperties properties; @Bean public DataSource dataSource() { 域名("数据库连接池创建中......"); HikariDataSource dataSource = null; try { dataSource = 域名te(域名lassLoader()) .type(域名s) .driverClassName(域名rmineDriverClassName()) .url(域名rmineUrl()) .username(域名rmineUsername()).password(域名assword()) .build(); } catch (Exception e) { throw new XfactorRuntimeException("get password failed!", e); } return dataSource; } @Bean public SqlSessionFactory xfactorSqlSessionFactory() throws Exception { MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean(); 域名ataSource(dataSource()); // 域名lugins(mybatisPlusInterceptor(), new AnalyseMybatisPluginsInterceptor()); 域名lugins(new XfactorResultSetHandlerInterceptor(), new XfactorParameterHandlerInterceptor(), new XfactorStatementHandlerInterceptor(), new XfactorExecutorInterceptor()); PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); 域名apperLocations(域名esources("classpath*:mapper/*xml")); 域名ypeAliasesPackage("域名域名.entity"); return 域名bject(); } @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); 域名nnerInterceptor(new PaginationInnerInterceptor(域名GRE_SQL)); return interceptor; } }
场景二:更改查询库表的schema(场景类似于修改sql语句),首先定义了一个XfactorStatementHandlerInterceptor,代码如下:
package 域名域名per; import 域名ection; import 域名域名域名ingStatementHandler; import 域名域名域名ementHandler; import 域名域名rceptor; import 域名域名rcepts; import 域名域名cation; import 域名域名ature; import 域名域名Object; import 域名域名emMetaObject; import 域名域名riProxyConnection; import 域名域名j; @Slf4j @Intercepts({ @Signature(type= 域名s, method = "prepare", args = {域名s, 域名s}), }) public class XfactorStatementHandlerInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { 域名("===StatementHandler==="); ((HikariProxyConnection)域名rgs()[0]).setSchema("notes");//这里改schema //这里改sql,但是如果是对select的sql语句进行修改,建议实现域名s的plugin中进行,当前方式改select语句insert/update/delete都会走这个判断 MetaObject metaObject = 域名bject(((RoutingStatementHandler)域名arget()).getBoundSql()); String execSql = (String) 域名alue("sql"); if(域名tsWith("select ") || 域名tsWith("SELECT ")) { 域名alue("sql", 域名at(" order by id desc")); } return 域名eed(); } }
结合以上两个场景可知,有些目的可以通过多个类型的plugin都能实现,但是肯定有一个是最佳方案的(plugin定义好以后,要想让插件起作用,需要把插件加入到MybatisSqlSessionFactoryBean中,代码见加粗的部分)。