mybatis拦截器
mybatis拦截器基础知识
编写类继承mybatis的Interceptor接口,并实现其相关方法。mybatis的拦截器,是被动态代理类主动调用的。
- intercept:在拦截时,需要执行的业务逻辑
- plugin:是否代理Executor、ParameterHandler、ResultSetHandler、StatementHandler对象中的某个或某些,
如果代理,则返回相应对象的代理对象,否则返回原对象。根据类上Intercepts注解决定是否返回代理对象。 - setProperties:配置时,向拦截器设置相关参数配置
Intercepts注解:设置该拦截器需要代理的类型及其方法
- Signature注解:拦截哪个类的哪个方法
- type参数:被代理对象类型,只能是Executor、ParameterHandler、ResultSetHandler、StatementHandler这几种的一种。
- method参数:被代理对象的方法,如Executor对象的update方法
- args参数:方法参数,Java是支持重载,因此需要参数和方法名才能唯一确定某类的某个方法
比如:我们需要代理Executor的query方法,而查询方法有2个,一个有(CacheKey cacheKey, BoundSql boundSql)参数一个没有,如果我们要代理的是没有这2个参数的方法,那么需要在配置中的args参数里就不写这2个参数
被代理类型执行顺序:Executor -> ParameterHandler -> ResultSetHandler -> StatementHandler
/**
* 这个示例表示该拦截器需要拦截Executor类的update和query方法
*/
@Intercepts({
@Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }),
@Signature(type = Executor.class, method = "query", args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
@Slf4j
public class MyBatisInterceptor implements Interceptor{
@Override
public Object intercept(Invocation invocation) throws Throwable {
//被代理对象的被代理方法参数,如Executor的update方法,那么args[0]为MappedStatement类型对象,args[1]为类型object对象
//args[1]具体为什么对象呢:当前执行查询的mapper方法对应的参数及其值
//如:当前我执行的是UserMapper的fundUser(@Param("user")User oneUser),那么这里的args[1]为一个map,key为user,值为形参oneUser对应的的值
Object[] args = invocation.getArgs();//
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
/**
* 向配置中添加拦截器
*/
@Configuration
public class MyBatisConfig {
@Autowired
private SqlSessionFactory sqlSessionFactory;
@PostConstruct
public void addMySqlInterceptor() {
MyBatisInterceptor interceptor = new MyBatisInterceptor();
sqlSessionFactory.getConfiguration().addInterceptor(interceptor);
}
}
mybatis拦截器原理
https://www.jianshu.com/p/b82d0a95b2f3
mybatis拦截器结合了动态代理和责任链2个模式。
动态代理类Plugin:
- 实现了InvocationHandler接口。
- 属性为目标对象和拦截器,通过私有的构造方法初始化数据
- invoke方法:调用拦截器的拦截方法。
- wrap静态方法:参数为目标对象和某个拦截器,如果某拦截器上的配置适合被代理类,则返回代理类,否则返回原始类。
拦截器类Interceptor:
- intercept方法:参数为Invocation,拦截器具体业务逻辑,并在合适的位置调用invocation对象的执行方法;
- wrap方法:该拦截器是否适用于被代理对象,一般直接调用Plugin的wrap方法即可。
责任链类InterceptorChain:
- 包含一个拦截器集合,并可以向拦截器集合添加拦截器。
- pluginAll方法:参数为被代理对象,遍历拦截器,并调用拦截器的wrap方法,将返回的结果作为下个拦截器调用wrap方法的参数。
假设被代理对象为A,有多个拦截器,如A1,A2,A3,且wrap都返回代理对象,那么最终的结果为A3代理了A2,A2代理了A1,A1代理了最原始的被代理对象A
被代理类信息Invocation:
- 里面包含被代理类型,被代理方法,方法参数,以及一个执行方法(通过反射的方式调用被代理类的被代理方法)。
- 被代理类可能是最原始的被代理对象,也可能是另外一个代理类
示例
需求:
- 使用mybatis实现对数据的逻辑删除。
- 如果执行删除操作,则将表中status字段修改成1
- 查询和修改时,需要将status=0作为where条件
- 对于新增和修改,如果有username和phone字段,则需要动态添加字段username_enc和phone_enc为对应字段的密文,并保存到数据库
根据上述需求,需要在mybatis拦截器中解析sql,并动态重写sql。
maven 依赖
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<!--<version>2.2.5.RELEASE</version>-->
<version>1.5.5.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>test-shardingsphere</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<description>Demo project for Spring Boot</description>
<dependencies>
<!--swagger ui-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<!--springboot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<!--mybatis starter-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.41</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.13</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--sql解析-->
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>3.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.2</version>
<configuration> <!--配置文件的位置-->
<configurationFile>src/main/resources/generatorConfig.xml</configurationFile>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
<executions>
<execution>
<id>Generate MyBatis Artifacts</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
mybatis拦截器配置
@Configuration
public class MyBatisConfig {
@Autowired
private SqlSessionFactory sqlSessionFactory;
@PostConstruct
public void addMySqlInterceptor() {
MyBatisInterceptor interceptor = new MyBatisInterceptor();
sqlSessionFactory.getConfiguration().addInterceptor(interceptor);
}
}
自定义拦截器
/**
*
*/
@Intercepts({
@Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }),
@Signature(type = Executor.class, method = "query", args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
@Slf4j
public class MyBatisInterceptor implements Interceptor{
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 拦截sql
Object[] args = invocation.getArgs();
MappedStatement statement = (MappedStatement) args[0];
Object parameterObject = args[1];
BoundSql boundSql = statement.getBoundSql(parameterObject);
String sql = boundSql.getSql();
if (sql==null||"".equals(sql)) {
return invocation.proceed();
}
// 重写sql
resetSql2Invocation(invocation,sql);
return invocation.proceed();
}
private void resetSql2Invocation(Invocation invocation,String sql) throws SQLException {
final Object[] args = invocation.getArgs();
MappedStatement statement = (MappedStatement) args[0];
MapperMethod.ParamMap parameterObject = (MapperMethod.ParamMap)args[1];
final BoundSql boundSql = statement.getBoundSql(parameterObject);
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
//根据不同的sql类型重新构建新的sql语句
String newsql = "";
MyOperationType myOperationType = null;
switch (statement.getSqlCommandType()) {
case INSERT:
myOperationType = new MyInsertOperationType();
newsql = myOperationType.handle(statement,parameterMappings, parameterObject, sql);
break;
case UPDATE:
myOperationType = new MyUpdateOperationType();
newsql = myOperationType.handle(statement,parameterMappings, parameterObject, sql);
break;
case DELETE:
myOperationType = new MyDeleteOperationType();
newsql = myOperationType.handle(statement,parameterMappings, parameterObject, sql);
break;
case SELECT:
myOperationType = new MySelectOperationType();
newsql = myOperationType.handle(statement,parameterMappings, parameterObject, sql);
break;
default:
break;
}
// 重新new一个查询语句对像
BoundSql newBoundSql = new BoundSql(statement.getConfiguration(), newsql, parameterMappings,
parameterObject);
// 把新的查询放到statement里
MappedStatement newStatement = copyFromMappedStatement(statement, new BoundSqlSqlSource(newBoundSql));
// 重新设置新的参数
args[0] = newStatement;
System.out.println("sql语句:"+newsql);
}
//构造新的statement
private MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource,
ms.getSqlCommandType());
builder.resource(ms.getResource());
builder.fetchSize(ms.getFetchSize());
builder.statementType(ms.getStatementType());
builder.keyGenerator(ms.getKeyGenerator());
if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {
StringBuilder keyProperties = new StringBuilder();
for (String keyProperty : ms.getKeyProperties()) {
keyProperties.append(keyProperty).append(",");
}
keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
builder.keyProperty(keyProperties.toString());
}
builder.timeout(ms.getTimeout());
builder.parameterMap(ms.getParameterMap());
builder.resultMaps(ms.getResultMaps());
builder.resultSetType(ms.getResultSetType());
builder.cache(ms.getCache());
builder.flushCacheRequired(ms.isFlushCacheRequired());
builder.useCache(ms.isUseCache());
return builder.build();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// TODO Auto-generated method stub
}
class BoundSqlSqlSource implements SqlSource {
private BoundSql boundSql;
public BoundSqlSqlSource(BoundSql boundSql) {
this.boundSql = boundSql;
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
return boundSql;
}
}
}
使用jsqlparser重写sql
jsqlparser基本api操作参考:https://blog.csdn.net/qq_35986709/article/details/109806402
public interface MyOperationType {
String handle(MappedStatement statement,List<ParameterMapping> parameterMappings, MapperMethod.ParamMap parameterObject, String sql);
default boolean isNotIn(ParameterMapping parameterMapping,List<ParameterMapping> parameterMappings){
if(parameterMappings!=null){
for(ParameterMapping it:parameterMappings){
if(it.getProperty().equals(parameterMapping.getProperty())){
return false;
}
}
}
return true;
}
default List<String> encList(String tableName,List<Column> columns){
List<String> result = new ArrayList<>();
List<String> list = getEncMap().get(tableName);
if(list!=null&&columns!=null){
for(Column col:columns){
if(list.contains(col.getColumnName())){
result.add(col.getColumnName());
}
}
}
return result;
}
default String getColumnValue(MapperMethod.ParamMap parameterObject,List<ParameterMapping> parameterMappings,List<Column> columns,String column){
if(columns!=null){
for(int i=0;i<columns.size();i++){
Column col = columns.get(i);
if(column.equals(col.getColumnName())){
ParameterMapping parameterMapping = parameterMappings.get(i);
String property = parameterMapping.getProperty();
Object result = parameterObject.get(property);
return (String)result;
}
}
}
return null;
}
Map<String,List<String>> getEncMap();
}
public abstract class MyDefaultOperationType implements MyOperationType{
Map<String,List<String>> encMaps = new HashMap<>();
/**
* 假设只对t_user表的username和phone2个字段进行加密存储
*/
public MyDefaultOperationType(){
encMaps.put("t_user", Arrays.asList("username","phone"));
}
@Override
public Map<String, List<String>> getEncMap() {
return encMaps;
}
}
public class MyInsertOperationType extends MyDefaultOperationType{
@Override
public String handle(MappedStatement mystatement,List<ParameterMapping> parameterMappings, MapperMethod.ParamMap parameterObject, String sql) {
try {
//解析sql语句
Statement statement = CCJSqlParserUtil.parse(sql);
//强制转换成insert对象
Insert insert = (Insert) statement;
//从insert中获取表名
Table table = insert.getTable();
//从insert中获取字段名
List<Column> columns = insert.getColumns();
List<String> needEncs = encList(table.getName(), columns);
if (!needEncs.isEmpty()) {
ExpressionList list = (ExpressionList) insert.getItemsList();
for(String col:needEncs) {
String newCol = col+"_enc";
Column miwen = new Column(newCol);
//在insert里添加一个字段
columns.add(miwen);
//在insert的value里添加一个占位符?
list.getExpressions().add(new JdbcParameter());
//将新增的字段添加到mybatis框架的字段集合中
ParameterMapping parameterMapping = new ParameterMapping.Builder(mystatement.getConfiguration(),newCol,String.class).build();
if(isNotIn(parameterMapping,parameterMappings)) {
parameterMappings.add(parameterMapping);
}
//添加参数值
parameterObject.put(newCol, getColumnValue(parameterObject,parameterMappings,columns,col)+"123");
}
insert.setItemsList(list);
}
return insert.toString();
}catch (Exception e){
throw new RuntimeException("解析sql异常",e);
}
}
}
public class MyUpdateOperationType extends MyDefaultOperationType {
@Override
public String handle(MappedStatement mystatement,List<ParameterMapping> parameterMappings, MapperMethod.ParamMap parameterObject, String sql) {
try {
Statement statement = CCJSqlParserUtil.parse(sql);
Update update = (Update)statement;
Table table = update.getTable();
List<Column> columns = update.getColumns();
List<String> needEncs = encList(table.getName(), columns);
if (!needEncs.isEmpty()) {
List<Expression> expressions = update.getExpressions();
for(String col:needEncs) {
String newCol = col+"_enc";
Column miwen = new Column(table,newCol);
Expression expression = new JdbcParameter();
expressions.add(expression);
//添加参数key
ParameterMapping parameterMapping = new ParameterMapping.Builder(mystatement.getConfiguration(),newCol,String.class).build();
if(isNotIn(parameterMapping,parameterMappings)) {
int index = parameterMappings.size()-(parameterMappings.size()- columns.size());
parameterMappings.add(index,parameterMapping);
}
columns.add(miwen);
//添加参数值
parameterObject.put(newCol, getColumnValue(parameterObject,parameterMappings,columns,col)+"123");
}
Expression where = update.getWhere();
EqualsTo status = new EqualsTo();
status.setLeftExpression(new Column(table, "status"));
StringValue stringValue = new StringValue("0");
status.setRightExpression(stringValue);
if(where!=null) {
AndExpression lastwhere = new AndExpression(where, status);
update.setWhere(lastwhere);
}else{
update.setWhere(status);
}
update.setExpressions(expressions);
}
return update.toString();
}catch (Exception e){
throw new RuntimeException("解析sql异常",e);
}
}
}
public class MyDeleteOperationType extends MyDefaultOperationType {
@Override
public String handle(MappedStatement mystatement,List<ParameterMapping> parameterMappings, MapperMethod.ParamMap parameterObject, String sql) {
try {
Statement statement = CCJSqlParserUtil.parse(sql);
Delete delete = (Delete) statement;
Table table = delete.getTable();
Update update = new Update();
update.setTable(table);
update.setColumns(Arrays.asList(new Column(table,"status")));
update.setExpressions(Arrays.asList(new StringValue("1")));
update.setWhere(delete.getWhere());
return update.toString();
}catch (Exception e){
throw new RuntimeException("解析sql异常",e);
}
}
}
public class MySelectOperationType extends MyDefaultOperationType {
@Override
public String handle(MappedStatement mystatement,List<ParameterMapping> parameterMappings, MapperMethod.ParamMap parameterObject, String sql) {
try {
Statement statement = CCJSqlParserUtil.parse(sql);
Select select = (Select) statement;
PlainSelect plain = (PlainSelect) select.getSelectBody();
FromItem fromItem = plain.getFromItem();
String tableName = fromItem.toString();
Table table = new Table();
table.setAlias(fromItem.getAlias());
table.setName(tableName);
Expression where = plain.getWhere();
EqualsTo status = new EqualsTo();
status.setLeftExpression(new Column(table, "status"));
StringValue stringValue = new StringValue("0");
status.setRightExpression(stringValue);
if(where!=null) {
AndExpression lastwhere = new AndExpression(where, status);
plain.setWhere(lastwhere);
}else{
plain.setWhere(status);
}
return select.toString();
}catch (Exception e){
throw new RuntimeException("解析sql异常",e);
}
}
}
版权声明:本文为zhaocuit原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。