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码云https://gitee.com/liudahai/notes,项⽬架构是springboot+mybatis+mybatis-plus,数据库我⽤的是postgresql-14),我认为这五个接⼝涵盖了我们在开发中90%的场景,根据打印的⽇志得到的结论是:
1. 两种查询、新增、修改、删除五个⽅法都会经过StatementHandler、ParameterHandler
2. 两种查询(单个查询、列表查询)都会经过Executor、StatementHandler、ParameterHandler、ResultSetHandler所以根据上⾯的结论,我们就可以来确定我们在开发中⽤哪种plugin,参考场景如下:
1. 如果想改⼊参,⽐如postgresql据库字段值⼤⼩写敏感,那么我可以在ParameterHandler⾥⾯获取到⼊参,然后toUpperCase();
2. 如果想改sql语句,⽐如改postgresql的schema,那么我可以在StatementHandler(prepare)⾥⾯获取到connection修改;若是查询场景也可以在Executor的query⽅法中获取connection修改;
3. 如果想对数据进⾏脱敏处理,⽐如查询场景下的,查出的结果中⾝份证显⽰前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\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!\ }
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\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\ }
return invocation.proceed(); }}
结合以上两个场景可知,有些⽬的可以通过多个类型的plugin都能实现,但是肯定有⼀个是最佳⽅案的(plugin定义好以后,要想让插件起作⽤,需要把插件加⼊到MybatisSqlSessionFactoryBean中,代码见加粗的部分)。
因篇幅问题不能全部显示,请点此查看更多更全内容