MyBatis-Plus 日志拦截器
EnhancedSqlLogInterceptor -> 拦截日志 SqlLogHandler -> 拓展功能:实现接口自定义处理日志
import com.baomidou.mybatisplus.core.toolkit.SystemClock;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.beans.factor #技术分享y.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Array; import java.sql.Statement; import java.text.SimpleDateFormat; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors;
@Slf4j @Component @Intercepts({ @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, org.apache.ibatis.session.ResultHandler.class}), @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}), @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class}) }) public class EnhancedSqlLogInterceptor implements Interceptor {
private static final long SLOW_SQL_THRESHOLD = 1000;
private static final Set<String> HIDE_PARAMS = new HashSet<>(Arrays.asList( "password", "pwd", "secret", "token", "creditCard", "cvv" ));
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("(?<!\\)\?(?!\?)");
private static final int MAX_LINE_LENGTH = 120;
@Autowired(required = false) private List<SqlLogHandler> sqlLogHandlers;
@Override public Object intercept(Invocation invocation) throws Throwable {
long startTime = SystemClock.now();
try { return invocation.proceed(); } finally { long endTime = SystemClock.now(); long costTime = endTime - startTime;
if (log.isDebugEnabled() || costTime > SLOW_SQL_THRESHOLD || (sqlLogHandlers != null && !sqlLogHandlers.isEmpty())) { String methodName = invocation.getMethod().getName();
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
BoundSql boundSql = statementHandler.getBoundSql();
String sqlWithPlaceholders = boundSql.getSql().trim();
String completeSql = getCompleteSql(statementHandler, boundSql, sqlWithPlaceholders); String logMsg = buildLogMessage(methodName, completeSql, costTime);
if(costTime > SLOW_SQL_THRESHOLD){ log.warn(logMsg); }else { if(log.isDebugEnabled()){ log.debug(logMsg); } } if(sqlLogHandlers != null && !sqlLogHandlers.isEmpty()){ for (SqlLogHandler sqlLogHandler : sqlLogHandlers) { sqlLogHandler.handle(methodName, completeSql, costTime); } } } } }
private String getCompleteSql(StatementHandler statementHandler, BoundSql boundSql, String sqlWithPlaceholders) { try { List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings == null || parameterMappings.isEmpty()) { return sqlWithPlaceholders; }
MetaObject metaObject = SystemMetaObject.forObject(statementHandler); MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement"); Configuration configuration = mappedStatement.getConfiguration();
Object parameterObject = boundSql.getParameterObject(); List<Object> paramValues = getParameterValues(boundSql, configuration, parameterObject);
Matcher matcher = PLACEHOLDER_PATTERN.matcher(sqlWithPlaceholders); StringBuffer sb = new StringBuffer(); int paramIndex = 0; while (matcher.find() && paramIndex < paramValues.size()) { String replacement = formatParameter(paramValues.get(paramIndex++)); matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement)); } matcher.appendTail(sb); return sb.toString();
} catch (Exception e) { log.error("获取完整 SQL 失败,将返回原始 SQL", e); return sqlWithPlaceholders; } }
private List<Object> getParameterValues(BoundSql boundSql, Configuration configuration, Object parameterObject) { List<Object> paramValues = new ArrayList<>(); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) { TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry(); MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject);
for (ParameterMapping parameterMapping : parameterMappings) { String propertyName = parameterMapping.getProperty();
if (isSensitiveParam(propertyName)) { paramValues.add("******"); continue; }
if (metaObject != null && typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { paramValues.add(parameterObject); } else if (boundSql.hasAdditionalParameter(propertyName)) { Object value = boundSql.getAdditionalParameter(propertyName); paramValues.add(value); } else if (parameterObject == null) { paramValues.add(null); } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { paramValues.add(parameterObject); } else if (metaObject != null) { Object value = metaObject.getValue(propertyName); paramValues.add(value); } else { paramValues.add(null); } } } return paramValues; }
private String formatParameter(Object param) { if (param == null) { return "NULL"; }
if (param instanceof String) { return "'" + param + "'"; }
if (param instanceof Date) { return "'" + DATE_FORMAT.format((Date) param) + "'"; }
if (param instanceof Number) { return param.toString(); }
if (param instanceof Boolean) { return Boolean.TRUE.equals(param) ? "1" : "0"; }
if (param instanceof byte[]) { return "[BINARY]"; }
if (param instanceof Collection) { return formatCollection((Collection<?>) param); }
if (param.getClass().isArray()) { return formatArray(param); }
return "'" + param.toString() + "'"; }
private String formatCollection(Collection<?> collection) { StringBuilder sb = new StringBuilder(); for (Object item : collection) { if (sb.length() > 0) sb.append(","); sb.append(formatParameter(item)); } return sb.toString(); }
private String formatArray(Object array) { StringBuilder sb = new StringBuilder(); int length = Array.getLength(array); for (int i = 0; i < length; i++) { if (sb.length() > 0) sb.append(","); sb.append(formatParameter(Array.get(array, i))); } return sb.toString(); }
private boolean isSensitiveParam(String paramName) { if (paramName == null) return false;
String lowerParamName = paramName.toLowerCase(); return HIDE_PARAMS.stream() .anyMatch(hideParam -> lowerParamName.contains(hideParam.toLowerCase())); }
private String buildLogMessage(String method, String sql, long costTime) { return String.format("\n" + "┌───────────────────────────────────────────────────────────────────────────────\n" + "│ SQL 方法: %s\n" + "│ 执行时间: %d ms\n" + "│ 完整 SQL: \n" + "%s" + "└───────────────────────────────────────────────────────────────────────────────", method, costTime, formatSqlForDisplay(sql)).replace("│ \n",""); }
private String formatSqlForDisplay(String sql) { List<String> formattedLines = new ArrayList<>(); StringBuilder currentLine = new StringBuilder();
String[] tokens = sql.split("\s+");
for (String token : tokens) { if (currentLine.length() + token.length() + 1 > MAX_LINE_LENGTH) { formattedLines.add(currentLine.toString()); currentLine = new StringBuilder(); }
if (currentLine.length() > 0) { currentLine.append(" "); } currentLine.append(token);
if (isKeyword(token)) { formattedLines.add(currentLine.toString()); currentLine = new StringBuilder(); } }
if (currentLine.length() > 0) { formattedLines.add(currentLine.toString()); }
return formattedLines.stream() .map(line -> "│ " + line) .collect(Collectors.joining("\n")) + "\n"; }
private boolean isKeyword(String token) { if (token == null || token.isEmpty()) { return false; }
String upperToken = token.toUpperCase(); return upperToken.equals("SELECT") || upperToken.equals("FROM") || upperToken.equals("WHERE") || upperToken.equals("JOIN") || upperToken.equals("INNER") || upperToken.equals("LEFT") || upperToken.equals("RIGHT") || upperToken.equals("GROUP") || upperToken.equals("ORDER") || upperToken.equals("HAVING") || upperToken.equals("LIMIT") || upperToken.equals("OFFSET") || upperToken.equals("UNION") || upperToken.equals("VALUES"); }
@Override public Object plugin(Object target) { return Plugin.wrap(target, this); }
@Override public void setProperties(Properties properties) { } }
SQL SqlLogHandler 接口
public interface SqlLogHandler {
void handle(String methodName, String completeSql, long costTime); }
SQL SqlLogHandler 实现类demo
import org.slf4j.Logger;
import org.springframework.stereotype.Component;
@Component public class DBLogHandler implements SqlLogHandler {
private static final Logger logger = org.slf4j.LoggerFactory.getLogger(DBLogHandler.class);
@Override public void handle(String methodName, String completeSql, long costTime) { logger.info("当前操作用户 :{}", SecurityUtils.getCurrentUserId()); logger.info("DBLogHandler{}: {} costTime: {}", methodName, completeSql, costTime); AsyncExecutor.execute(() -> logger.info("异步执行器 DBLogHandler{}: {} costTime: {}", methodName, completeSql, costTime)); } }