Closed wswiftly closed 1 month ago
at com.shkp.mybatis.aspect.handle.DataUpdateInterceptor.getStatement(DataUpdateInterceptor.java:197) at com.shkp.mybatis.aspect.handle.DataUpdateInterceptor.intercept(DataUpdateInterceptor.java:49) 这里不是已经告诉你调用方出的问题了么
在 com.shkp.mybatis.aspect.handle.DataUpdateInterceptor.getStatement(DataUpdateInterceptor.java:197) 在 com.shkp.mybatis.aspect.handle.DataUpdateInterceptor.intercept(DataUpdateInterceptor.java:49) 這裡不是已經告訴你調用方出的問題了麼
感谢大佬答复, 昨天排查问题的时候发现这个拦截器有用到反射获取参数类型, 于是把这个拦截器注释掉了,今天测试了两三个小时暂时没出现这个错, 疑惑的是这是个多模块项目,其他子模块也是用这个拦截器并没有出现这种情况,只有其中一个子模块会出现。 拦截器代码如下:
package com.shkp.mybatis.aspect.handle;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.core.toolkit.TableNameParser;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.shkp.mybatis.aspect.annotation.IgnoreDataLog;
import com.shkp.mybatis.aspect.utils.SqlUtil;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionUtils;
import org.springframework.util.CollectionUtils;
import java.lang.reflect.Proxy;
import java.sql.Statement;
import java.util.*;
/**
* 数据更新拦截器
*/
@Log4j2
@AllArgsConstructor
@Intercepts({@Signature(type = StatementHandler.class, method = "update", args = {Statement.class})})
public class DataUpdateInterceptor implements Interceptor {
// @SuppressWarnings({"deprecation", "unused"})
@Override
@SneakyThrows
public Object intercept(Invocation invocation) {
// 判断是否需要记录日志
if (BaseDataLog.DATA_CHANGES.get() == null) {
return invocation.proceed();
}
// Statement statement = null;
// statement = getStatement(invocation);
StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget());
// String originalSql = statementHandler.getBoundSql().getSql().replaceAll("\n", StringPool.SPACE);
// originalSql = originalSql.replaceAll("[\\s]+", StringPool.SPACE);
// int index = indexOfSqlStart(originalSql);
// if (index > 0) {
// originalSql = originalSql.substring(index);
// }
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
// 将参数和映射文件组合在一起得到BoundSql对象
BoundSql boundSql = statementHandler.getBoundSql();
// 获取配置信息
Configuration configuration = mappedStatement.getConfiguration();
// 通过配置信息和BoundSql对象来生成带值得sql语句
String sql = SqlUtil.showSql(configuration, boundSql);
// 获取执行Sql
sql = sql.replace("where", "WHERE");
// 使用mybatis-plus 工具解析sql获取表名
Collection<String> tables = new TableNameParser(sql).tables();
if (CollectionUtils.isEmpty(tables)) {
return invocation.proceed();
}
String tableName = tables.iterator().next();
// 排除表名判断
for (String string : tables) {
if (BaseDataLog.excludeTableNames.contains(string)) {
return invocation.proceed();
}
}
// 使用mybatis-plus 工具根据表名找出对应的实体类
TableInfo tableInfo = TableInfoHelper.getTableInfos().stream()
.filter(item -> item.getTableName().equals(tableName))
.findFirst()
.orElseThrow(() -> new IllegalStateException("未找到表名为: {} 的对应的实体类: " + tableName));
Class<?> entityType = tableInfo.getEntityType();
if (entityType == null || entityType.isAnnotationPresent(IgnoreDataLog.class)) {
return invocation.proceed();
}
DataChange change = new DataChange();
change.setTableName(tableName);
change.setEntityType(entityType);
// 插入
if (SqlCommandType.INSERT.equals(mappedStatement.getSqlCommandType())) {
BaseDataLog.DATA_SQL.set(sql);
BaseDataLog.DATA_TYPE.set(SqlCommandType.INSERT);
BaseDataLog.DATA_CHANGES.get().add(change);
}
// 更新
if (SqlCommandType.UPDATE.equals(mappedStatement.getSqlCommandType())) {
BaseDataLog.DATA_SQL.set(sql);
BaseDataLog.DATA_TYPE.set(SqlCommandType.UPDATE);
// 设置sql用于执行完后查询新数据
String selectSql = "AND " + sql.substring(sql.lastIndexOf("WHERE") + 5);
// 同表对同条数据操作多次只进行一次对比
if (BaseDataLog.DATA_CHANGES.get().stream()
.anyMatch(c -> tableName.equals(c.getTableName()) && selectSql.equals(c.getWhereSql()))) {
return invocation.proceed();
}
change.setWhereSql(selectSql);
Map<String, Object> map = new HashMap<>(1);
map.put(Constants.WRAPPER, Wrappers.query().eq("1", 1).last(selectSql));
// 查询更新前数据
SqlSessionFactory sqlSessionFactory = SqlHelper.sqlSessionFactory(entityType);
change.setSqlSessionFactory(sqlSessionFactory);
change.setSqlStatement(tableInfo.getSqlStatement(SqlMethod.SELECT_LIST.getMethod()));
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
List<?> oldData = sqlSession.selectList(change.getSqlStatement(), map);
change.setOldData(Optional.ofNullable(oldData).orElse(new ArrayList<>()));
} finally {
SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory);
}
BaseDataLog.DATA_CHANGES.get().add(change);
}
// 删除
if (SqlCommandType.DELETE.equals(mappedStatement.getSqlCommandType())) {
BaseDataLog.DATA_SQL.set(sql);
BaseDataLog.DATA_TYPE.set(SqlCommandType.DELETE);
BaseDataLog.DATA_CHANGES.get().add(change);
}
return invocation.proceed();
}
/**
* 获取更新的类
*
* @param invocation
* @return
*/
public Class<?> getClassType(Invocation invocation) {
try {
String sql = getOriginSql(invocation);
Collection<String> tables = new TableNameParser(sql).tables();
if (CollectionUtils.isEmpty(tables)) {
return null;
}
String tableName = tables.iterator().next();
TableInfo tableInfo = TableInfoHelper.getTableInfos().stream().filter(item -> item.getTableName().equals(tableName)).findFirst()
.orElseThrow(() -> new IllegalStateException("未找到表名为: {} 的对应的实体类: " + tableName));
// TableInfo tableInfo = TableInfoHelper.getTableInfos().stream().filter(item -> item.getTableName().equals(tableName)).findFirst().orElse(null);
// TableInfo tableInfo = TableInfoHelper.getTableInfos().stream().filter(item -> item.getTableName().equals(tableName)).findFirst().orElse(new TableInfo(null, null));
Class<?> entityType = tableInfo.getEntityType();
return entityType;
} catch (Exception e) {
return null;
}
}
/**
* 获取SQL语句
*
* @param invocation
* @return
*/
public String getOriginSql(Invocation invocation) {
Statement statement = getStatement(invocation);
String originalSql = statement.toString();
return originalSql;
}
/**
* 获取Statement
*
* @param invocation
* @return
*/
public Statement getStatement(Invocation invocation) {
Object firstArg = invocation.getArgs()[0];
Statement statement = null;
if (Proxy.isProxyClass(firstArg.getClass())) {
statement = (Statement) SystemMetaObject.forObject(firstArg).getValue("h.statement");
} else {
statement = (Statement) firstArg;
}
MetaObject stmtMetaObj = SystemMetaObject.forObject(statement);
try {
statement = (Statement) stmtMetaObj.getValue("stmt.statement");
} catch (Exception e) {
// do nothing
}
if (stmtMetaObj.hasGetter("delegate")) {
// Hikari
try {
statement = (Statement) stmtMetaObj.getValue("delegate");
} catch (Exception ignored) {
}
}
return statement;
}
/**
* 获取sql语句开头部分
*
* @param sql ignore
* @return ignore
*/
private int indexOfSqlStart(String sql) {
String upperCaseSql = sql.toUpperCase();
Set<Integer> set = new HashSet<>();
set.add(upperCaseSql.indexOf("SELECT "));
set.add(upperCaseSql.indexOf("UPDATE "));
set.add(upperCaseSql.indexOf("INSERT "));
set.add(upperCaseSql.indexOf("DELETE "));
set.remove(-1);
if (CollectionUtils.isEmpty(set)) {
return -1;
}
List<Integer> list = new ArrayList<>(set);
list.sort(Comparator.naturalOrder());
return list.get(0);
}
}
SqlUtil代码如下:
package com.shkp.mybatis.aspect.utils;
import cn.hutool.core.convert.Convert;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.util.CollectionUtils;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.regex.Matcher;
public class SqlUtil {
/**
* 获取aop中的SQL语句
*
* @param pjp
* @param sqlSessionFactory
* @return
* @throws IllegalAccessException
*/
@SuppressWarnings("unchecked")
public static String getMybatisSql(ProceedingJoinPoint pjp, SqlSessionFactory sqlSessionFactory,
MybatisSqlSessionFactoryBean mybatisSqlSessionFactory) throws IllegalAccessException {
Map<String, Object> map = new HashMap<>();
// 1.获取namespace+methdoName
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
String namespace = method.getDeclaringClass().getName();
String methodName = method.getName();
// 2.根据namespace+methdoName获取相对应的MappedStatement
Configuration configuration = sqlSessionFactory.getConfiguration();
MappedStatement mappedStatement = configuration.getMappedStatement(namespace + "." + methodName);
// //3.获取方法参数列表名
// Parameter[] parameters = method.getParameters();
// 4.形参和实参的映射
Object[] objects = pjp.getArgs(); // 获取实参
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
for (int i = 0; i < parameterAnnotations.length; i++) {
Object object = objects[i];
if (parameterAnnotations[i].length == 0) { // 说明该参数没有注解,此时该参数可能是实体类,也可能是Map,也可能只是单参数
if (object.getClass().getClassLoader() == null && object instanceof Map) {
map.putAll((Map<? extends String, ?>) object);
System.out.println("该对象为Map");
} else {// 形参为自定义实体类
map.putAll(objectToMap(object));
System.out.println("该对象为用户自定义的对象");
}
} else {// 说明该参数有注解,且必须为@Param
for (Annotation annotation : parameterAnnotations[i]) {
if (annotation instanceof Param) {
map.put(((Param) annotation).value(), object);
}
}
}
}
// 5.获取boundSql
BoundSql boundSql = mappedStatement.getBoundSql(map);
return showSql(configuration, boundSql);
}
/**
* 解析BoundSql,生成不含占位符的SQL语句
*
* @param configuration
* @param boundSql
* @return
*/
public static String showSql(Configuration configuration, BoundSql boundSql) {
// 获取参数
Object parameterObject = boundSql.getParameterObject();
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
// sql语句中多个空格都用一个空格代替
String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
if (!CollectionUtils.isEmpty(parameterMappings) && parameterObject != null) {
// 获取类型处理器注册器,类型处理器的功能是进行java类型和数据库类型的转换
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
// 如果根据parameterObject.getClass()可以找到对应的类型,则替换
if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(parameterObject)));
} else {
// MetaObject主要是封装了originalObject对象,提供了get和set的方法用于获取和设置originalObject的属性值,主要支持对JavaBean、Collection、Map三种类型对象的操作
MetaObject metaObject = configuration.newMetaObject(parameterObject);
for (ParameterMapping parameterMapping : parameterMappings) {
String propertyName = parameterMapping.getProperty();
if (metaObject.hasGetter(propertyName)) {
Object obj = metaObject.getValue(propertyName);
sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
} else if (boundSql.hasAdditionalParameter(propertyName)) {
// 该分支是动态sql
Object obj = boundSql.getAdditionalParameter(propertyName);
sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
} else {
// 打印出缺失,提醒该参数缺失并防止错位
sql = sql.replaceFirst("\\?", "缺失");
}
}
}
}
return sql;
}
/**
* 若为字符串或者日期类型,则在参数两边添加''
*
* @param obj
* @return
*/
private static String getParameterValue(Object obj) {
String value;
if (obj instanceof String) {
value = "'" + obj.toString() + "'";
} else if (obj instanceof LocalDateTime) {
LocalDateTime time = Convert.convert(LocalDateTime.class, obj);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
value = "to_date('" + formatter.format(time) + "','YYYY-MM-DD HH24:Mi:SS')";
} else if (obj instanceof Date) {
Date date = Convert.convert(Date.class, obj);
DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
value = "to_date('" + formatter.format(date) + "','YYYY-MM-DD HH24:Mi:SS')";
// value = "to_date('" + formatter.format(new Date()) + "','YYYY-MM-DD HH24:Mi:SS')";
} else {
if (obj != null) {
value = obj.toString();
} else {
value = "''";
}
}
return value;
}
/**
* 获取利用反射获取类里面的值和名称
*
* @param obj
* @return
* @throws IllegalAccessException
*/
private static Map<String, Object> objectToMap(Object obj) throws IllegalAccessException {
Map<String, Object> map = new HashMap<>();
Class<?> clazz = obj.getClass();
System.out.println(clazz);
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
String fieldName = field.getName();
Object value = field.get(obj);
map.put(fieldName, value);
}
return map;
}
}
确认
当前程序版本
3.5.7
问题描述
详细堆栈日志