Toasobi
mybatis实现数据库慢sql监控
本文最后更新于2023年08月29日,已超过496天没有更新。如果文章内容或图片资源失效,请留言反馈,我会及时处理,谢谢!
mybatis实现数据库慢sql监控
@Intercepts 解释:
@Intercepts是MyBatis框架提供的注解,用于标识一个拦截器需要拦截的方法。
在这个例子中,@Intercepts注解标识了一个拦截器需要拦截Executor接口的query方法。Executor是MyBatis中负责执行 > SQL语句的核心接口,而query方法是执行查询操作的方法。
拦截器可以拦截多个方法,所以@Intercepts注解可以接收一个数组参数,每个元素代表一个拦截点。在这个例子中,有两个拦> 截点,分别对应了不同的方法参数列表。
第一个拦截点的参数列表为MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class,> 表示拦截Executor接口的query方法,该方法接收MappedStatement、Object、RowBounds和ResultHandler四个参数。
第二个拦截点同上
@Component
@Intercepts({
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})
}
)
public class MybatisSqlInterceptor implements Interceptor {
private int ERROR_TIME = 1000;
private int WARN_TIME = 100;
private static final Logger logger = LoggerFactory.getLogger(MybatisSqlInterceptor.class);
@Override
public Object intercept(Invocation invocation) throws Throwable {
//方法参数
Object[] args = invocation.getArgs();
Date begin = new Date();
Object ret = invocation.proceed();
Date end = new Date();
try {
MappedStatement mappedStatement = (MappedStatement) args[0];
// 方法参数
Object params = args[1];
// mapper 中的方法名称,如 “com.xushu.mysql.slowsql.dao.AccountMapper.selectList”
String mapperId = mappedStatement.getId();
// 获取 sql 语句,并格式化 sql,将很多空格替换成一个空格
String sql = formatSql(mappedStatement.getBoundSql(params).getSql());
// 做日志输出
log(mapperId, sql, params, begin, end);
} catch (Exception e) {
e.printStackTrace();
logger.error(e.getMessage(),e);
}
return ret;
}
private String formatSql(String sql) {
return sql.replaceAll("\\s+", " ");
}
private void log(String statementId, String sql, Object params, Date begin, Date end) {
long ms = end.getTime() - begin.getTime();
String paramStr = parseParams(params);
if (ms > ERROR_TIME) {
logger.error("MAPPER_NAME:" + statementId + "\n" + "TIME:" + ms + " ms; SQL:[" + sql + "]; PARAM:[" + paramStr + "]");
} else if (ms > WARN_TIME) {
logger.warn("MAPPER_NAME:" + statementId + "\n" + "TIME:" + ms + " ms; SQL:[" + sql + "]; PARAM:[" + paramStr + "]");
} else {
// logger.info("MAPPER_NAME:" + statementId + "\n" + "TIME:" + ms + " ms; SQL:[" + sql + "]; PARAM:[" + paramStr + "]");
}
}
/**
* @description 格式化传参
* @author kongxiangneng
* @time 2023/8/16 10:38
*/
private String parseParams(Object params) {
StringBuilder sb = new StringBuilder();
try {
if (params instanceof Map) {
Map map = (Map) params;
map.forEach((k, v) -> {
sb.append(",").append(k).append(":").append(v);
});
} else if (BeanUtils.isSimpleProperty(params.getClass())) {
sb.append(",").append(params.getClass().getSimpleName()).append(":").append(params).append(",");
} else {
// 反射getter属性
PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(params.getClass());
for (PropertyDescriptor pd : pds) {
if (pd.getReadMethod() != null && !"class".equals(pd.getName())) {
String name = pd.getName();
Object value = null;
value = pd.getReadMethod().invoke(params);
sb.append(",").append(name).append(":").append(value);
}
}
}
} catch (Exception e) {
}
if (sb.length() > 0) {
return sb.toString().substring(1);
}
return "";
}
@Override
public Object plugin(Object o) {
return Plugin.wrap(o,this) ;
}
@Override
public void setProperties(Properties properties) {
}
}
在这个监控模式下,项目会打印日志记录执行时间较长的日志(根据时间划分等级为warn和error)
整合了ELFK之后,可以再Kibana上看到日志信息