MyBatis 允许拦截的接口
MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
Executor接口的部分方法,比如update,query,commit,rollback等方法,还有其他接口的一些方法等。
总体概括为:
- 拦截执行器的方法
- 拦截参数的处理
- 拦截结果集的处理,为sql执行之后的结果拦截过滤
- 拦截Sql语法构建的处理,为sql执行之前的拦截进行sql封装
MyBatis拦截器的接口定义
一共有三个方法intercept
、plugin
、setProperties
setProperties()
方法主要是用来从配置中获取属性。
如果是使用xml式配置拦截器,可在Mybatis配置文件中添加如下节点,属性可以以如下方式传递
<plugins>
<plugin interceptor="tk.mybatis.simple.plugin.XXXInterceptor">
<property name="propl" value="valuel" />
<property name="prop2" value="value2" />
</plugin>
</plugins>
如果在Spring Boot 中使用配置类,则需要单独写一个配置类 ,如下:
@Configuration
public class MybatisInterceptorConfig{
@Bean
public String myInterceptor(SqlSessionFactory sqlSessionFactory){
ExecutorInterceptor executorInterceptor = new ExecutorInterceptor();
Properties properties = new Properties();
properties.setProperty("prop1","value1");
executorInterpecetor.setProperties(properties);
return "interceptor";
}
}
如果说不需要配置属性,则在spring boot
中,不需要去编写配置类,只需要像我一样在拦截器上加个@Component
即可。
plugin()
方法用于指定哪些方法可以被此拦截器拦截。
intercept()
方法是用来对拦截的sql
进行具体的操作。
注解实现
MyBatis
拦截器用到了两个注解:@Intercepts
和@Signature
@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}),
}
)
type
的值与类名相同,method
与方法名相同,为了避免方法重载,args
中指定了各个参数的类型和个数,可通过invocation.getArgs()
获取参数数组。
Executor 拦截器实现
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class AutoFillInterceptor implements Interceptor {
private static final String CREATE_BY = "createBy";
private static final String UPDATE_BY = "updateBy";
private static final String CREATE_TIME = "createTime";
private static final String UPDATE_TIME = "updateTime";
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
// 获取用于描述SQL语句的映射信息
MappedStatement ms = (MappedStatement) args[0];
SqlCommandType sqlCommandType = ms.getSqlCommandType();
// 获取sql参数实体 ParamMap
Object parameter = args[1];
if (parameter != null && sqlCommandType != null) {
// 获取用户ID
Long userId = loadUserId();
if (SqlCommandType.INSERT.equals(sqlCommandType)) {
// 插入操作
if (parameter instanceof MapperMethod.ParamMap) {
// 批量插入的情况
MapperMethod.ParamMap paramMap = (MapperMethod.ParamMap) parameter;
ArrayList list = (ArrayList) paramMap.get("list");
list.forEach(v -> {
// 设置创建人和创建时间字段值
setFieldValByName(CREATE_BY, userId, v);
setFieldValByName(CREATE_TIME, LocalDateTime.now(), v);
setFieldValByName(UPDATE_TIME, LocalDateTime.now(), v);
});
paramMap.put("list", list);
} else {
// 单条插入的情况
// 设置创建人和创建时间字段值
setFieldValByName(CREATE_BY, userId, parameter);
setFieldValByName(CREATE_TIME, LocalDateTime.now(), parameter);
setFieldValByName(UPDATE_TIME, LocalDateTime.now(), parameter);
}
} else if (SqlCommandType.UPDATE.equals(sqlCommandType)) {
// 更新操作
// 设置更新人和更新时间字段值
setFieldValByName(UPDATE_BY, userId, parameter);
setFieldValByName(UPDATE_TIME, LocalDateTime.now(), parameter);
}
}
// 继续执行原始方法
return invocation.proceed();
}
/**
* 通过反射设置实体的字段值
* @param fieldName 字段名
* @param fieldVal 字段值
* @param parameter 实体对象
*/
private void setFieldValByName(String fieldName, Object fieldVal, Object parameter) {
MetaObject metaObject = SystemMetaObject.forObject(parameter);
if (fieldName.equals(CREATE_BY)) {
Object value = metaObject.getValue(fieldName);
if (ObjectUtil.isNotEmpty(value)) {
return;
}
}
if (metaObject.hasSetter(fieldName)) {
metaObject.setValue(fieldName, fieldVal);
}
}
@Override
public Object plugin(Object target) {
if (target instanceof Executor) {
// 对目标对象进行包装,返回代理对象
return Plugin.wrap(target, this);
}
// 非 Executor 类型的对象,直接返回原始对象
return target;
}
@Override
public void setProperties(Properties properties) {
// 读取配置文件中的属性,此处没有使用
}
/**
* 获取当前用户的ID,用于填充创建人和更新人字段的值
*
* @return 当前用户ID
*/
public static Long loadUserId() {
// 从 ThreadLocal 中获取用户ID
Long userId = UserThreadLocal.getUserId();
// 如果 ThreadLocal 中不存在用户ID,则从管理用户ID中获取
if (ObjectUtil.isNotEmpty(userId)) {
return userId;
}
userId = UserThreadLocal.getMgtUserId();
// 如果管理用户ID也不存在,则默认返回ID为1的用户
if (!EmptyUtil.isNullOrEmpty(userId)) {
return userId;
}
return 1L;
}
}
这段代码是一个 MyBatis 的拦截器(Interceptor),用于实现自动填充实体对象中的创建人、更新人、创建时间和更新时间字段的值。下面是代码的主要逻辑解释:
-
在
AutoFillInterceptor
类中实现了Interceptor
接口,并在@Intercepts
注解中指定了拦截Executor
类中的update
方法,参数为MappedStatement.class
和Object.class
。 -
在
intercept
方法中,通过拦截器的Invocation
对象获取方法参数,并判断 SQL 命令类型(插入或更新)。 -
根据 SQL 命令类型,分别对参数进行处理:
- 对于插入操作,根据参数类型(单条插入或批量插入),设置创建人、创建时间和更新时间字段的值。
- 对于更新操作,设置更新人和更新时间字段的值。
-
使用反射方式设置实体对象的字段值,通过
MetaObject
和SystemMetaObject
类实现字段值的设置。 -
plugin
方法用于包装目标对象,这里对Executor
对象进行包装并返回代理对象。 -
setProperties
方法用于设置配置属性,这里没有使用。 -
loadUserId
方法用于获取当前用户的ID,从UserThreadLocal
中获取用户ID,如果不存在则返回默认值 1。
这段代码主要实现了在数据库操作时自动填充实体对象的特定字段,提高了开发效率和代码复用性。通过拦截器的方式,实现了对数据库操作的统一处理,避免了重复的代码编写。