目的:业务数据表在有DML语句时,将变更数据记录到历史表中,便于数据回溯。
1.定义拦截器
@Slf4j
//@Component
@Intercepts({
@Signature(type = Executor.class,
method = "update",
args = {MappedStatement.class, Object.class}),
})
public class SelfInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
// 获取MappedStatement
MappedStatement mappedStatement = (MappedStatement) args[0];
Object param = null;
//获取类型
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
if(SqlCommandType.DELETE.equals(sqlCommandType) ||
SqlCommandType.UPDATE.equals(sqlCommandType) ||
SqlCommandType.INSERT.equals(sqlCommandType)){
// 获取SQL语句的ID,哪个方法执行
String sqlId = mappedStatement.getId();
if(args.length>=2){
param = args[1];
}
// // 获取SQL语句
// BoundSql boundSql = mappedStatement.getBoundSql(args[1]);
// String sql = boundSql.getSql();
}
try {
return invocation.proceed();
} finally {
if(param!=null&&SqlUtil.getLocalHistory()) {
SqlUtil.clearLocalHistory();
// 变更信息入表
HistoryInsertService.insertIntoHistory(SqlUtil.getTableName(),param);
}
}
}
@Override
public Object plugin(Object target) {
// 使用 Plugin.wrap 方法生成代理对象
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// 获取配置文件中的属性值
}
}
拦截器加入Mybatis configuration
- Springboot-Mybatis-start可以直接使用@Component加入
- Spring环境可以在Mybatis配置文件加入
<plugins>
<plugin interceptor="com.xxx.SelfInterceptor" />
</plugins>
2.自定义注解 && 设置上下文环境
(1)注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface History {
String table();
}
table为业务数据表名
(2)上下文环境
@Aspect
@Component
@Order(2)
public class HistoryAspectConfig {
@Pointcut("@annotation(com.xxx.History)")
public void cut() {}
@Before("cut() && @annotation(history)")
public void before(History history){
SqlUtil.setLocalHistory();
SqlUtil.setTableName(history.table());
}
/**
* 无论什么情况下都会执行的方法
*/
@After("@annotation(com.xxx.History)")
public void after(){
SqlUtil.clearLocalHistory();
SqlUtil.clearTableName();
}
}
注解加到Mapper方法上面,在mapper执行前,设置标识符,标记此次方法需要增加
after将Threadlocal清理,确保无内存泄露问题
public class SqlUtil {
private static ThreadLocal<String> localHistory = new ThreadLocal<>();
private static ThreadLocal<String> tableName = new ThreadLocal<>();
public static void setLocalHistory(){
localHistory.set("Yeah");
}
public static void clearLocalHistory(){
localHistory.remove();
}
public static boolean getLocalHistory(){
return "Yeah".equals(localHistory.get());
}
public static void clearTableName(){
tableName.remove();
}
public static void setTableName(String name){
tableName.set(name);
}
public static String getTableName(){
return tableName.get();
}
}
localHistory记录标识,用于判断此sql是否需要记录到变更表中
tableName用于记录业务表的名称
3.将业务表变更的数据入库(记录版本)
@Service("historyInsertService")
public class HistoryInsertService {
private static HistoryTestMapper historyTestMapper;
@Autowired
public void setHistoryTestMapper(HistoryTestMapper historyTestMapper) {
HistoryInsertService.historyTestMapper = historyTestMapper;
}
public static void insertIntoHistory(String table,Object data){
String jsonString = JSON.toJSONString(data);
// 将 JSON 字符串转换为 Map
Map<String, Object> jsonMap = JSON.parseObject(jsonString, new TypeReference<Map<String, Object>>() {});
String id = jsonMap.get("id").toString();
if(StringUtils.isBlank(id)){
return;
}
HistoryTest historyTest = new HistoryTest();
historyTest.setData(jsonString);
historyTest.setTableName(table);
historyTest.setDataId(id);
List<HistoryTest> historyTests = historyTestMapper.selectByExample(historyTest);
if(historyTests==null||historyTests.isEmpty()){
historyTest.setVersion("1");
}else {
historyTest.setVersion(String.valueOf((historyTests.size()+1)));
}
historyTestMapper.insert(historyTest);
}
}