Mybatis拦截慢SQL日志记录

背景

  • 通过分析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;
    // 执行SQL
    private String executeSql;
    // SQL类型
    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 {

    // 日志记录mapper
    @Autowired
    private LogMapper logMapper;

    // 是否插入数据库标识
    @Value("${slow-sql.insertDB}")
    private boolean isInsertDB;

    // 超时时间
    @Value("${slow-sql.timeout}")
    private Long timeout;

    /**
     * @Author thailandking
     * @Date 2020/2/16 16:44
     * @LastEditors thailandking
     * @LastEditTime 2020/2/16 16:44
     * @Description sql拦截器
     */
    @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();
                // 参数map
                Object parameterObject = boundSql.getParameterObject();
                // 参数列表
                List<ParameterMapping> parameterMappingList = boundSql.getParameterMappings();
                // 格式化sql语句,去除换行符,替换参数
                sql = formatSQL(sql, parameterObject, parameterMappingList);
                log.error("执行 SQL:[ {} ], 执行耗时[ {} ms ]", sql, (end - begin));
                // 判断是否写入数据库
                if (isInsertDB) {
                    // 转换参数map为String
                    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) {

    }
}

效果展示

在这里插入图片描述

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值