TypechoJoeTheme

Toasobi的博客

mybatis实现数据库慢sql监控

本文最后更新于2023年08月29日,已超过387天没有更新。如果文章内容或图片资源失效,请留言反馈,我会及时处理,谢谢!

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上看到日志信息
朗读
赞(0)
评论 (0)