前言

Mybatis拦截器注解的基本写法格式为:

1
2
3
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})

说明:
@Intercepts:标识该类是一个拦截器
@Signature:拦截器相关属性设置

type

拦截器的类型,总共有4种,按照执行的先后顺序为:

  1. Executor:拦截执行器的方法。
  2. ParameterHandler:拦截参数的处理。
  3. ResultHandler:拦截结果集的处理。
  4. StatementHandler:拦截Sql语法构建的处理,绝大部分我们是在这里设置我们的拦截器。

图片

method

可被拦截的方法,按照拦截器的不同类型,总共有下面这些方法:

拦截的类型 拦截的方法
Executor update, query, flushStatements, commit, rollback,getTransaction, close, isClosed
ParameterHandler getParameterObject, setParameters
StatementHandler prepare, parameterize, batch, update, query
ResultSetHandler handleResultSets, handleOutputParameters

args

设置对应接口中的哪一个方法,比如 Executorquery方法因为重载原因,方法有多个,args 就是指明参数类型,从而确定是哪一个方法。

这里的方法和上面的方法的要怎么理解呢?可以这样理解,上面的那个方法(method)是大类的方法,比如update方法,下面的这个方法(args)呢,是具体的方法,用来指明你到底的哪里的update方法。

注册拦截器

使用配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--其它配置项都在著配置文件配置了,所以这里只需要配置configuration项即可-->
<configuration>
<settings>
<!--是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<plugins>
<plugin interceptor="com.example.IbatisInterceptor"/>
</plugins>
</configuration>

然后在主配置文件中,将mybatis配置文件引入:

1
2
3
# Mybatis配置
mybatis:
config-location: classpath:mybatis/config/mybatis-config.xml # mybatis配置文件,如果使用configuration的话就不需要mybatis配置文件了

todo:
mybatis的配置项中,有一个interceptors配置,其参数是一个列表(java.util.List)。通过查阅相关信息发现,不能在配置文件中引用自定义的拦截器。总之是暂时没找到 具体使用方法,先放着吧。

1
2
3
4
5
6
7
8
9
# Mybatis配置
mybatis:
# config-location: classpath:mybatis/config/mybatis-config.xml # mybatis配置文件,如果使用configuration的话就不需要mybatis配置文件了
mapper-locations: classpath:mybatis/*.xml # mybatis映射文件
configuration: # mybatis全局配置
map-underscore-to-camel-case: true #开启驼峰命名规则
interceptors:
- xxx
- xxxxx

使用配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import com.example.IbatisInterceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Properties;


@Configuration
public class IbatisInterceptorConfig {

@Bean
public String MyBatisInterceptor(SqlSessionFactory sqlSessionFactory) {
IbatisInterceptor ibatisInterceptor = new IbatisInterceptor();
Properties properties = new Properties();
properties.setProperty("driver", "mysql");
properties.setProperty("username", "root");
properties.setProperty("password", "123456");

// 给拦截器添加自定义参数
ibatisInterceptor.setProperties(properties);
sqlSessionFactory.getConfiguration().addInterceptor(ibatisInterceptor);
return "interceptor";
}

}

使用注解

直接在拦截器类上面使用@Component注解即可(本文中就是直接使用的注解)

使用拦截器

使用mybatis拦截器,需要实现Interceptor接口的三个方法:

  1. intercept():当方法被拦截时调用,用于设置拦截后需要执行的业务逻辑,自定义拦截器时,此方法是必须实现的。
  2. plugin():在mybatis加载配置时被调用。设置是否需要对对象进行拦截
  • 拦截对象:调用wrap()方法,返回一个代理对象,并执行intercept()方法
  • 不拦截对象:直接返回目标原本对象,不会执行intercept()方法
  1. setProperties():在mybatis加载配置时被调用。获取并设置mybatis配置文件或配置类中的property属性的值,配置类见上面注册拦截器时的演示

mybatis配置文件演示:

1
2
3
4
5
6
7
8
9
10
11
12
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--mybatis配置项,上面的setProperties()方法中获取的propertys就是这里的-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>

原理图:
图片

简单使用

以拦截执行器方法为例。
示例:拦截更新操作,在更新操作时统一添加 “更新时间 ”字段,同时打印当前sql语句。

从语义角度分析:
insert、delete、update都是属于更新(update)操作;
而select属于查询(query)操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

import com.example.bean.User;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.sql.Date;
import java.time.LocalDateTime;
import java.util.Properties;


@Component
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
public class IbatisInterceptor implements Interceptor {

private Logger logger = LoggerFactory.getLogger(IbatisInterceptor.class);

@Override
public Object intercept(Invocation invocation) throws Throwable {

Object[] args = invocation.getArgs();
SqlCommandType sqlCommandType = null;

for (Object object : args) {

// 从MappedStatement中获取到操作类型
if (object instanceof MappedStatement) {
MappedStatement mappedStatement = (MappedStatement) object;
sqlCommandType = mappedStatement.getSqlCommandType();

// 获取当前擦操作的sql语句
BoundSql boundSql = mappedStatement.getBoundSql(object);
String sql = boundSql.getSql();

// 打印当前操作类型及当前操作的sql语句
logger.info("操作类型: {},操作语句:{}", sqlCommandType, sql);
}
// 判断参数的类型是否和实体类一致(这里只有一个参数,所以直接用if判断)
if (object instanceof User) {
if (SqlCommandType.UPDATE == sqlCommandType) {
// 由于没有写额外的获取实体类属性的方法,所以这里只能利用反射机制,获取需要修改的字段对应的实体类属性
Field updateTime = object.getClass().getDeclaredField("updateTime");
updateTime.setAccessible(true);
// 对数据进行修改
updateTime.set(object, Date.valueOf(LocalDateTime.now().toLocalDate()));
}

}
}
return invocation.proceed();
}

@Override
public Object plugin(Object target) {
// 拦截属于Executor的对象
if (target instanceof Executor) {
// 返回代理
return Plugin.wrap(target, this);
}
// 否则返回对象本身
return target;
}

@Override
public void setProperties(Properties properties) {

}
}

拦截多个请求

示例:只做简单示例,不涉及业务逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89

import com.example.bean.User;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.sql.PreparedStatement;
import java.util.Properties;


@Component
@Intercepts({@Signature(type = Executor.class,method = "query", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "createCacheKey", args = {MappedStatement.class, Object.class, RowBounds.class, BoundSql.class}),
@Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),

})
public class IbatisInterceptor implements Interceptor {

private Logger logger = LoggerFactory.getLogger(IbatisInterceptor.class);

@Override
public Object intercept(Invocation invocation) throws Throwable {

Object[] args = invocation.getArgs();
SqlCommandType sqlCommandType = null;

for (Object object : args) {

// 从MappedStatement中获取到操作类型
if (object instanceof MappedStatement) {
MappedStatement mappedStatement = (MappedStatement) object;
sqlCommandType = mappedStatement.getSqlCommandType();

// 判断参数的类型是否和实体类一致(这里只有一个参数,所以直接用if判断)
if (object instanceof User) {
if (SqlCommandType.UPDATE == sqlCommandType) {
// 执行更新操作修改
} else if (SqlCommandType.SELECT == sqlCommandType) {
// 执行查询操作修改
} else if (SqlCommandType.DELETE == sqlCommandType) {
// 执行删除操作修改
} else if (SqlCommandType.INSERT == sqlCommandType) {
// 执行插入操作修改
}
}
}else if(object instanceof PreparedStatement){
// 从PreparedStatement中获取到操作类型
PreparedStatement preparedStatement = (PreparedStatement) object;
// doSomething
}
// 判断参数的类型是否和实体类一致(这里只有一个参数,所以直接用if判断)
if (object instanceof User) {
if (SqlCommandType.UPDATE == sqlCommandType) {
// 执行更新操作修改
} else if (SqlCommandType.SELECT == sqlCommandType) {
// 执行查询操作修改
} else if (SqlCommandType.DELETE == sqlCommandType) {
// 执行删除操作修改
} else if (SqlCommandType.INSERT == sqlCommandType) {
// 执行插入操作修改
}
}
}
return invocation.proceed();
}

@Override
public Object plugin(Object target) {
if (target instanceof Executor) {
return Plugin.wrap(target, this);
}
return target;
}

@Override
public void setProperties(Properties properties) {
// String username = properties.getProperty("username");
// String password = properties.getProperty("password");
}
}

拦截并修改sql语句

从上面的示例中我们能够发现,是可以在拦截器中获取到sql语句的,那么我们就可以通过反射修改sql语句。
将通过id删除数据的sql语句改为通过name删除的sql语句:
有两种方法:
第一种是通过Statement修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.plugin.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.Properties;


@Component
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
public class IbatisInterceptor implements Interceptor {

private Logger logger = LoggerFactory.getLogger(IbatisInterceptor.class);

@Override
public Object intercept(Invocation invocation) throws Throwable {

Object[] args = invocation.getArgs();
SqlCommandType sqlCommandType = null;

for (Object object : args) {

// 从MappedStatement中获取到操作类型
if (object instanceof MappedStatement) {
MappedStatement mappedStatement = (MappedStatement) object;
sqlCommandType = mappedStatement.getSqlCommandType();

// 获取当前擦操作的sql语句
BoundSql boundSql = mappedStatement.getBoundSql(object);
String sql = boundSql.getSql();

// 打印当前操作类型及当前操作的sql语句
logger.info("操作类型: {},操作语句:{}", sqlCommandType, sql);

// 设置新的sql语句
String newSql = "delete from users where u_name=?";

/*
* 通过statement修改sql语句
*/

// 重新new一个查询语句对像
BoundSql newBoundSql = new BoundSql(mappedStatement.getConfiguration(), newSql, boundSql.getParameterMappings(), boundSql.getParameterObject());

// 把新的查询放到statement里
MappedStatement neMappedStatement = copyFromMappedStatement(mappedStatement, new BoundSqlSqlSource(newBoundSql));
for (ParameterMapping mapping : boundSql.getParameterMappings()) {
String prop = mapping.getProperty();
if (boundSql.hasAdditionalParameter(prop)) {
newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));
}
}

final Object[] queryArgs = invocation.getArgs();
// 这个i就是设置你需要修改哪个拦截的请求,对应上面的@Intercepts注解中的数组中的元素(@Signature注解)对应的索引
// 因为我上面只有一个(@Signature注解的)元素,对应的索引自然也就是0
int i = 0;
queryArgs[i] = neMappedStatement;
}
}
return invocation.proceed();
}

@Override
public Object plugin(Object target) {
// 拦截属于Executor的对象
if (target instanceof Executor) {
// 返回代理
return Plugin.wrap(target, this);
}
// 否则返回对象本身
return target;
}

@Override
public void setProperties(Properties properties) {

}

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) {
builder.keyProperty(ms.getKeyProperties()[0]);
}
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();
}

public static class BoundSqlSqlSource implements SqlSource {
private BoundSql boundSql;

public BoundSqlSqlSource(BoundSql boundSql) {
this.boundSql = boundSql;
}

public BoundSql getBoundSql(Object parameterObject) {
return boundSql;
}
}
}


第二种是直接通过反射修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.Properties;


@Component
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
public class IbatisInterceptor implements Interceptor {

private Logger logger = LoggerFactory.getLogger(IbatisInterceptor.class);

@Override
public Object intercept(Invocation invocation) throws Throwable {

Object[] args = invocation.getArgs();
SqlCommandType sqlCommandType = null;

for (Object object : args) {

// 从MappedStatement中获取到操作类型
if (object instanceof MappedStatement) {
MappedStatement mappedStatement = (MappedStatement) object;
sqlCommandType = mappedStatement.getSqlCommandType();

// 获取当前擦操作的sql语句
BoundSql boundSql = mappedStatement.getBoundSql(object);
String sql = boundSql.getSql();

// 打印当前操作类型及当前操作的sql语句
logger.info("操作类型: {},操作语句:{}", sqlCommandType, sql);

// 设置新的sql语句
String newSql = "delete from users where u_name=?";

// 通过反射修改sql语句
Field field = boundSql.getClass().getDeclaredField("sql");
field.setAccessible(true);
field.set(boundSql, newSql);

}
}
return invocation.proceed();
}

@Override
public Object plugin(Object target) {
// 拦截属于Executor的对象
if (target instanceof Executor) {
// 返回代理
return Plugin.wrap(target, this);
}
// 否则返回对象本身
return target;
}

@Override
public void setProperties(Properties properties) {

}

}

最后来个图吧:
图片