背景
- 通过分析MySQL慢查询日志不方便,利用数据源不直观,无法了解上下文调用关系
- 要求通过可配置化的操作实现日志在控制台的输出和在数据库中记录
- 本文利用SpringBoot和Mybatis,基于Mybatis Interceptor实现慢SQL拦截日志记录和打印输出
一、服务配置
# slow-sql
# 基于interceptor
slow-sql-interceptor.enabled=true
# 设置超时时间ms
slow-sql.timeout=10
# 日志是否插入数据库
slow-sql.insertDB=true
二、日志实体类
@Data
public class SqlLog {
private Long id;
private String methodName;
private String params;
private Long runTime;
private String executeSql;
private String sqlType;
private Date createTime;
}
三、SQL拦截器
@Intercepts({@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
@Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
@Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})})
@Component
@Slf4j
@ConditionalOnProperty(prefix = "slow-sql-interceptor", name = "enabled", havingValue = "true")
public class SqlExecuteTimeInterceptor implements Interceptor {
@Autowired
private LogMapper logMapper;
@Value("${slow-sql.insertDB}")
private boolean isInsertDB;
@Value("${slow-sql.timeout}")
private Long timeout;
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object target = invocation.getTarget();
long begin = System.currentTimeMillis();
StatementHandler statementHandler = (StatementHandler) target;
try {
return invocation.proceed();
} finally {
long end = System.currentTimeMillis();
if ((end - begin) > timeout) {
MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
String methodName = mappedStatement.getId();
String sqlType = mappedStatement.getSqlCommandType().toString();
BoundSql boundSql = statementHandler.getBoundSql();
String sql = boundSql.getSql();
Object parameterObject = boundSql.getParameterObject();
List<ParameterMapping> parameterMappingList = boundSql.getParameterMappings();
sql = formatSQL(sql, parameterObject, parameterMappingList);
log.error("执行 SQL:[ {} ], 执行耗时[ {} ms ]", sql, (end - begin));
if (isInsertDB) {
String params = changeSqlMapParams(parameterObject, parameterMappingList);
SqlLog log = new SqlLog();
log.setMethodName(methodName);
log.setParams(params);
log.setRunTime((end - begin));
log.setExecuteSql(sql);
log.setSqlType(sqlType);
logMapper.insertSqlLog(log);
}
}
}
}
private String changeSqlMapParams(Object parameterObject, List<ParameterMapping> parameterMappingList) {
StringBuilder sb = new StringBuilder();
Map<String, Object> params = (Map<String, Object>) parameterObject;
for (ParameterMapping pm : parameterMappingList) {
if (pm.getMode().name().equals("IN")) {
String param = ",[" + params.get(pm.getProperty()).toString() + "]";
sb.append(param);
}
}
return sb.length() > 0 ? sb.substring(1) : null;
}
private String formatSQL(String sql, Object parameterObject, List<ParameterMapping> parameterMappingList) {
if (sql == null || sql.length() == 0) {
return "";
}
sql = sql.replaceAll("[\\s\n ]+", " ");
Map<String, Object> params = (Map<String, Object>) parameterObject;
for (ParameterMapping pm : parameterMappingList) {
if (pm.getMode().name().equals("IN")) {
sql = sql.replaceFirst("\\?", params.get(pm.getProperty()).toString());
}
}
return sql;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
效果展示
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200424112935728.png)