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 com.lhclab.xfactor.dal.wrapper; import java.lang.reflect.Field; import java.sql.Statement; import java.util.List; import org.apache.commons.codec.binary.StringUtils; import org.apache.ibatis.executor.resultset.ResultSetHandler; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.plugin.Intercepts; import org.apache.ibatis.plugin.Invocation; import org.apache.ibatis.plugin.Signature; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.SystemMetaObject; import lombok.extern.slf4j.Slf4j; @Slf4j @Intercepts({ @Signature(type= ResultSetHandler.class,method = "handleResultSets",args = {Statement.class}) }) public class XfactorResultSetHandlerInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { log.info("===ResultSetHandler==="); Object resultSet = invocation.proceed(); List resultList = (List)resultSet; for(Object item : resultList) { Class<?> sourceClass = item.getClass(); MetaObject metaObject = SystemMetaObject.forObject(item); Field[] fields = sourceClass.getDeclaredFields(); for(Field field : fields) { if(StringUtils.equals(field.getName(), "password")) { metaObject.setValue(field.getName(), "******"); } } } return resultSet; } }
plugin定义好以后,要想让插件起作用,需要把插件加入到MybatisSqlSessionFactoryBean中,代码如下(见标黄的部分)
package com.lhclab.xfactor.dal.config; import javax.sql.DataSource; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean; import com.lhclab.xfactor.common.exception.XfactorRuntimeException; import com.lhclab.xfactor.dal.wrapper.XfactorExecutorInterceptor; import com.lhclab.xfactor.dal.wrapper.XfactorParameterHandlerInterceptor; import com.lhclab.xfactor.dal.wrapper.XfactorResultSetHandlerInterceptor; import com.lhclab.xfactor.dal.wrapper.XfactorStatementHandlerInterceptor; import com.zaxxer.hikari.HikariDataSource; import lombok.extern.slf4j.Slf4j; @Slf4j @Configuration @MapperScan("com.lhclab.xfactor.dal.dao") public class DataSourceConfig { @Autowired private DataSourceProperties properties; @Bean public DataSource dataSource() { log.info("数据库连接池创建中......"); HikariDataSource dataSource = null; try { dataSource = DataSourceBuilder.create(properties.getClassLoader()) .type(HikariDataSource.class) .driverClassName(properties.determineDriverClassName()) .url(properties.determineUrl()) .username(properties.determineUsername()).password(properties.getPassword()) .build(); } catch (Exception e) { throw new XfactorRuntimeException("get password failed!", e); } return dataSource; } @Bean public SqlSessionFactory xfactorSqlSessionFactory() throws Exception { MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSource()); // sqlSessionFactoryBean.setPlugins(mybatisPlusInterceptor(), new AnalyseMybatisPluginsInterceptor()); sqlSessionFactoryBean.setPlugins(new XfactorResultSetHandlerInterceptor(), new XfactorParameterHandlerInterceptor(), new XfactorStatementHandlerInterceptor(), new XfactorExecutorInterceptor()); PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath*:mapper/*xml")); sqlSessionFactoryBean.setTypeAliasesPackage("com.lhclab.xfactor.dal.dao.entity"); return sqlSessionFactoryBean.getObject(); } @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.POSTGRE_SQL)); return interceptor; } }
场景二:更改查询库表的schema(场景类似于修改sql语句),首先定义了一个XfactorStatementHandlerInterceptor,代码如下:
package com.lhclab.xfactor.dal.wrapper; import java.sql.Connection; import org.apache.ibatis.executor.statement.RoutingStatementHandler; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.plugin.Intercepts; import org.apache.ibatis.plugin.Invocation; import org.apache.ibatis.plugin.Signature; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.SystemMetaObject; import com.zaxxer.hikari.pool.HikariProxyConnection; import lombok.extern.slf4j.Slf4j; @Slf4j @Intercepts({ @Signature(type= StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}), }) public class XfactorStatementHandlerInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { log.info("===StatementHandler==="); ((HikariProxyConnection)invocation.getArgs()[0]).setSchema("notes");//这里改schema //这里改sql,但是如果是对select的sql语句进行修改,建议实现Executor.class的plugin中进行,当前方式改select语句insert/update/delete都会走这个判断 MetaObject metaObject = SystemMetaObject.forObject(((RoutingStatementHandler)invocation.getTarget()).getBoundSql()); String execSql = (String) metaObject.getValue("sql"); if(execSql.startsWith("select ") || execSql.startsWith("SELECT ")) { metaObject.setValue("sql", execSql.concat(" order by id desc")); } return invocation.proceed(); } }
结合以上两个场景可知,有些目的可以通过多个类型的plugin都能实现,但是肯定有一个是最佳方案的(plugin定义好以后,要想让插件起作用,需要把插件加入到MybatisSqlSessionFactoryBean中,代码见加粗的部分)。