场景:
记录有过增删改操作的数据表及最后更新时间,实现数据增量同步。
解决方案:基于Mybatis拦截器开发自定义拦截器
1.实现Mybatis拦截器Interceptor,重写intercept方法
2.使用druid解析sql,获取sql中涉及的表
3.实现自定义逻辑
拦截器代码如下:
package com.cms.filter;
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor;
import com.alibaba.druid.stat.TableStat;
import com.alibaba.druid.util.JdbcConstants;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.cms.common.utils.StringUtils;
import com.cms.common.utils.spring.SpringUtils;
import com.cms.domain.entity.systemData.SystemData;
import com.cms.mapper.systemData.SystemDataMapper;
import lombok.extern.slf4j.Slf4j;
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.springframework.stereotype.Component;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author daixin
* @version 1.0
* @description: TODO
* @date 2022/9/28 13:48
*/
@Slf4j
@Component
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
public class ExecutorDataVaryInterceptor implements Interceptor {
/**
* 拦截方法逻辑
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
//方法参数
Object[] args = invocation.getArgs();
MappedStatement mappedStatement = (MappedStatement) args[0];
Object parameterObject = args[1];
//获取Sql命令类型UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
String commandType = sqlCommandType.name();
//获取拦截的Sql
BoundSql boundSql = mappedStatement.getSqlSource().getBoundSql(parameterObject);
String sql = boundSql.getSql();
//判断新增和修改操作,执行自定义逻辑
if("INSERT".equalsIgnoreCase(commandType)||"UPDATE".equalsIgnoreCase(commandType)){
//解析Sql, 获取涉及的数据表
List<String> tableNameList = getTableNameBySql(sql);
//判断sql执行的数据表是否在需要管理的白名单内
QueryWrapper<SystemData> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().in(SystemData::getTableName,tableNameList);
SystemDataMapper systemDataMapper = SpringUtils.getBean(SystemDataMapper.class);
List<SystemData> systemDataList = systemDataMapper.selectList(queryWrapper);
//在白名单内的表, 管理更新时间
if(!StringUtils.isEmpty(systemDataList)){
List<String> updateTimeTables = systemDataList.stream().map(SystemData::getTableName).collect(Collectors.toList());
systemDataMapper.saveUpdateTimeBatch(updateTimeTables);
}
}
Object result = invocation.proceed();
return result;
}
/**
* 解析Sql,获取涉及的数据表
* @param sql
* @return
*/
private List<String> getTableNameBySql(String sql) {
try {
String dbType = String.valueOf(JdbcConstants.MYSQL);
List<String> tableNameList = new ArrayList<>();
log.info("格式化后的sql:{}",SQLUtils.format(sql, dbType));
List<SQLStatement> sqlStatementList = SQLUtils.parseStatements(sql, dbType);
if (CollectionUtils.isEmpty(sqlStatementList)) {
return Collections.emptyList();
}
for (SQLStatement sqlStatement : sqlStatementList) {
MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor();
sqlStatement.accept(visitor);
Map<TableStat.Name, TableStat> tables = visitor.getTables();
for (TableStat.Name name : tables.keySet()) {
String tableName = name.getName();
if (StringUtils.isNotBlank(tableName)) {
tableNameList.add(tableName);
}
}
}
log.info("解析sql获取的表名:{}",tableNameList);
return tableNameList;
} catch (Exception e) {
log.error("===================异常SQL=================={}",sql);
log.error(e.getMessage(),e);
}
return Collections.emptyList();
}
}
mybatis-config.xml注入拦截器
<plugins>
<plugin interceptor="com.cms.filter.ExecutorDataVaryInterceptor">
</plugin>
</plugins>
spring boot集成druid所需依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
<scope>compile</scope>
</dependency>
同理,也可实现日志收集等功能。