参考资料:
一. 🤔需求场景
如下图所示
- 有一个订单信息表,每当插入或者更新一条数据的时候,红框所示部分的字段都需要进行赋值处理.
- 如果还有其他表中也存在红框中的所示字段,每次插入或者更新时也都需要进行赋值操作,很是繁琐。
🐳有没有一种办法,能再插入或者更新数据库指定字段的时候,自动替我们处理呢?这就用到了Mybatis中的拦截器.
二. 🤠前期准备
⏹定义一个接口,用来标记实现了此接口的类中的参数会被自动处理
public interface AutoHandleParam {
}
⏹定义order_info
表的映射实体类
import lombok.Data;
import java.util.Date;
@Data
public class OrderInfoEntity implements AutoHandleParam {
private String orderId;
private String orderName;
private String createUserId;
private Date createTime;
private String updateUserId;
private Date updateTime;
}
⏹查询接口
public interface OrderInfoMapper {
void insertOrderInfo(OrderInfoEntity entity);
void updateOrderInfo(OrderInfoEntity entity);
}
⏹反射封装类ReflectionUtil
,详情见这篇文章 Java 封装反射工具类
三. 💪自定义拦截器
import com.example.jmw.common.utils.ReflectionUtil;
import com.example.jmw.entity.AutoHandleParam;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Properties;
@Component
@Intercepts({
@Signature(
type = Executor.class, method = "update",
args = {MappedStatement.class, Object.class}
)
})
public class MybatisParamInterceptor implements Interceptor {
// 需要拦截的Mybatis中的操作
private final static String INSERT = "INSERT";
private final static String UPDATE = "UPDATE";
private final static List<String> sqlCommandTypeList = Arrays.asList(INSERT, UPDATE);
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
// 获取Mybatis的当前操作方法名称
String sqlCommandType = mappedStatement.getSqlCommandType().name();
if (!sqlCommandTypeList.contains(sqlCommandType)) {
return invocation.proceed();
}
if (invocation.getArgs().length < 2) {
return invocation.proceed();
}
// 获取Mybatis插入或更新时传入的参数对象
Object paramEntity = invocation.getArgs()[1];
this.interceptInsertOrUpdateMethod(sqlCommandType, paramEntity);
return invocation.proceed();
}
/**
* 插入或更新时的参数拦截方法
* @param methodName Mybatis方法名称
* @param paramEntity 参数实体类
*/
private void interceptInsertOrUpdateMethod(String methodName, Object paramEntity) {
// 如果该对象没有实现AutoHandleParam接口的话,就不需要自动添加参数
if (!(paramEntity instanceof AutoHandleParam)) {
return;
}
// 系统默认的用户名
String userId = "系统用户名" + System.currentTimeMillis();
// 当前的系统时间
Date systemDate = new Date();
// 若Mybatis的当前方法为INSERT
if (INSERT.equals(methodName)) {
// 若实体类中存在此字段
if (ReflectionUtil.existsField(paramEntity, "createUserId")) {
Object createUserId = ReflectionUtil.invokeGetterMethod(paramEntity, "createUserId");
if (ObjectUtils.isEmpty(createUserId) || "".equals(createUserId.toString())) {
// 通过反射将用户ID放入实体类中
ReflectionUtil.invokeSetterMethod(paramEntity, "createUserId", userId);
}
}
if (ReflectionUtil.existsField(paramEntity, "createTime")) {
Object createTime = ReflectionUtil.invokeGetterMethod(paramEntity, "createTime");
if (ObjectUtils.isEmpty(createTime) ) {
ReflectionUtil.invokeSetterMethod(paramEntity, "createTime", systemDate);
}
}
return;
}
// 若Mybatis的当前方法为UPDATE
if (UPDATE.equals(methodName)) {
if (ReflectionUtil.existsField(paramEntity, "updateUserId")) {
ReflectionUtil.invokeSetterMethod(paramEntity, "updateUserId", userId);
}
if (ReflectionUtil.existsField(paramEntity, "updateTime")) {
ReflectionUtil.invokeSetterMethod(paramEntity, "updateTime", systemDate);
}
}
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
四. 👉添加自定义拦截器到Mybatis中
- 将自定义拦截器添加到Mybatis中,有两种方式
-
- 自定义一个方法,返回
SqlSessionFactory
到Spring的Bean中。
- 自定义一个方法,返回
-
- 自定义一个方法,返回
SqlSessionFactoryBeanCustomizer
到Spring的Bean中。
- 自定义一个方法,返回
-
- 官方文件推荐将
SqlSessionFactoryBeanCustomizer
到Spring的Bean中,将SqlSessionFactory
到Spring的Bean中的这种方式,会导致Mybatis的配置文件或配置类失效。因为无论配置文件还是配置类,最终都是加载到SqlSessionFactory
中,如果我们自己手动new一个SqlSessionFactory
到Bean中的话,相当于重置了Mybatis为我们自动配置好的SqlSessionFactory
。
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.boot.autoconfigure.SqlSessionFactoryBeanCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
import javax.sql.DataSource;
@Configuration
public class DataSourceConfig {
// 注入数据源
@Resource
private DataSource dataSource;
// 注入自定义参数拦截器
@Resource
private MybatisParamInterceptor paramInterceptor;
// @Bean ===> ❗❗❗会造成Mybatis配置文件或者配置类失效,尽量不要这么写。
public SqlSessionFactory sqlSessionFactorySecondary() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
// 设置数据源
sqlSessionFactoryBean.setDataSource(dataSource);
// 设置拦截器(可设置多个)
Interceptor[] plugins = {paramInterceptor};
sqlSessionFactoryBean.setPlugins(plugins);
return sqlSessionFactoryBean.getObject();
}
// 💪💪💪官方推荐的实现方式
@Bean
SqlSessionFactoryBeanCustomizer sqlSessionFactoryBeanCustomizer() {
return new SqlSessionFactoryBeanCustomizer() {
@Override
public void customize(SqlSessionFactoryBean customizeFactoryBean) {
// 设置数据源
customizeFactoryBean.setDataSource(dataSource);
// 设置自定义拦截器(可设置多个)
Interceptor[] plugins = {paramInterceptor};
customizeFactoryBean.setPlugins(plugins);
}
};
}
}
👉👉👉 官方文档截图
五. 😋效果
⭕操作之前,表中没有数据
⏹插入一条数据
- 可以看到我们并没有向参数实体类中传入用户ID和创建时间,但是拦截器替我们向实体类中放入了参数,因此最终用户ID和创建时间插入到数据库中了。
⏹更新一条数据
- 可以看到指定的订单名称被更新,并且更新用户ID和更新时间也被自动处理到数据库中