一、什么是Mybatis拦截器
Mybatis拦截器是mybatis
提供的一套接口,用于拦截mabatis
访问数据库时的行为,并允许我们在拦截中,添加自己需要的自定义操作。
二、 如何使用拦截器添加参数
先给一段代码:
@Intercepts(
{@Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
@Signature(
type = Executor.class,
method = "update",
args = {MappedStatement.class, Object.class}),
})
public class MybatisInterceptor implements Interceptor {
private static final Logger logger = LoggerFactory.getLogger(MybatisInterceptor.class);
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
MappedStatement mappedStatements = (MappedStatement) args[0];
Map<String, Object> paramMap = new MapperMethod.ParamMap<>();
if (args[1] != null) {
if (args[1] instanceof MapperMethod.ParamMap) {
paramMap.putAll((Map) args[1]);
} else if (BeanUtils.isSimpleValueType(args[1].getClass())) {
String mapperId = mappedStatements.getId();
Class<?> paramType = args[1].getClass();
String clazzName = mapperId.substring(0, mapperId.lastIndexOf("."));
String methodName = mapperId.substring(mapperId.lastIndexOf(".") + 1);
Class<?> target = Class.forName(clazzName);
Method method = target.getMethod(methodName, paramType);
Parameter[] params = method.getParameters();
String paramName = params[0].getName();
paramMap.put(paramName, args[1]);
} else {
List<Field> fields = new ArrayList<>();
getAllFields(fields, args[1].getClass());
for (Field field : fields) {
Object fieldValue = null;
String fieldName = field.getName();
try {
fieldValue = (new PropertyDescriptor(fieldName, args[1].getClass())).getReadMethod().invoke(args[1]);
} catch (Exception var9) {
logger.error(var9.getMessage(), var9);
}
paramMap.putIfAbsent(field.getName(), fieldValue);
}
}
}
paramMap.put("key", "value");
args[1] = paramMap;
return invocation.proceed();
}
private List<Field> getAllFields(List<Field> fields, Class<?> type) {
fields.addAll(Arrays.asList(type.getDeclaredFields()));
if (type.getSuperclass() != null) {
this.getAllFields(fields, type.getSuperclass());
}
return fields;
}
}
实现一个拦截器主要有3步。
1 实现Interceptor接口
Interceptor
接口是mbatis
提供的拦截器接口。mybatis
会执行该接口声明的方法去拦截对应的操作。
Interceptor
接口有三个方法,一个必须实现的方法,两个使用Interceptor
的默认实现就够用的方法。
1)Object intercept(Invocation var1)
必须实现的方法,使用该方法在拦截中途添加自己想要的自定义操作。
2)plugin(Object target)
可选的方法,将插件添加到mybatis
操作中。
3)setProperties(Properties properties)
可选的方法,读取配置中的属性。
2 添加@Intercepts注解
@Intercepts
注解的作用是,标记需要拦截的方法列表。mybatis
通过该注解去判断当前方法是否需要被拦截。
@Intercepts
其实就是一个数组,用来添加复数个@Signature
。
每个@Signature
都指定了一个需要拦截的方法。
@Signature
注解有3个参数。
1)type
指定拦截的类的类型,type
有4个类型可选
Executor.class
、ParameterHandler.class
、ResultSetHandler.class
、StatementHandler.class
2)method
指定拦截的方法名
3)args
指定方法的参数类型,去对应的方法里看有哪些参数类型就可以了。如果填错会报错。
mybatis
会根据这三个参数找到对应的方法,并进行拦截。
mybatis
给出了允许拦截的方法列表
Executor (update, query, flushStatements, commit, rollback,getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets,handleOutputParameters)
StatementHandler (prepare, parameterize,batch, update, query)
例子
@Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
这个注解声明了,要拦截的类是
Executor.class
要拦截的方法名是
query
该方法的参数有4个,类型是
MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class
3 intercept方法详解
intercept
方法用来在拦截时添加自定义的操作。
该方法提供给我们一个参数:Invocation invocation
Invocation
类有3个字段,
private final Object target;
private final Method method;
private final Object[] args;
target
被拦截的类
method
被拦截的方法
args
方法的实际参数
既然我们要添加参数到拦截的方法里,所以我们需要关心的主要就是这个args
。
args
是一个参数数组,里面的参数数量对应着@Signature
声明的那几个参数,也就是拦截方法的参数。
举个例子:
@Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
拦截该方法时,args就是一个长度为4的数组,里面4个参数是
MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class
其中,第二个参数,也就是args[1]
,是实际执行Mapper
时传入的参数,后续sql会根据args[1]
里的值来填充sql
。
所以我们要做的就是把自己的参数添加到args[1]
中。
添加参数也是有说法的。args[1]
是一个Object
类型,实际类型会根据Mapper
方法的参数数量和类型发生变化。
因此需要分多种情况考虑。
1)无参数
Mapper
执行的是一个无参方法时,args[1]
为null
。
需要创建一个MapperMethod.ParamMap
对象,然后将自定义的参数添加进去,再赋值给arg[1]
就可以了。
MapperMethod.ParamMap是一个继承于Map的类。把他当作一个Map<String,Object>就可以了。
2)一个参数
Mapper
执行的是一个单一参数方法时,args[1]
是Object
类型。
需要创建一个MapperMethod.ParamMap
对象,将已经有的参数放进去,然后将自定义参数放进去。
如果args[1]
是一个基本数据类型,就存在一个问题,Map
是key-value
结构,args[1]
只有值,没有参数名。我们知道value
是不够的,sql解析时根据参数名去找对应的value。
所以我们需要去反射实际执行的mapper
,拿到对应的参数名。
如果args[1]
是一个引用类型,我们就需要解析他所有的字段,将字段名和其值组成key-value键值对,存储到Map
中。
因为单一参数是引用时,mybatis
会直接忽略参数名,直接匹配参数中的字段名。
3)多个参数
Mapper
执行的是一个多参数方法时,args[1]
已经是MapperMethod.ParamMap
类型。
所以我们无需修改args[1]
的类型,直接将自定义的参数添加进去就可以了。
注意点
@Param
注解修饰的参数虽然参数名称发生改变,但无需特别处理,因为@Param
注解并非修改源参数的名称,而是添加了一个新的参数。
三、应用场景
可以将创建者,更新者,这类通用参数通过拦截器添加到参数中,无需手动添加该参数,也不用写AOP方法。
可以做分库分表,根据上下文将实际操作的数据库名传入其中,无需手动判断操作的是哪个数据库。