首页 > 数据库 >Mybatis 之 SQL 监控插件

Mybatis 之 SQL 监控插件

时间:2022-10-28 23:01:45浏览次数:68  
标签:插件 String SQL value boundSql sql Mybatis

请结合上一篇 >>> MP 插件原理  <<<   

以及   >>>  Mybatis 插件原理 <<<  进行查看查看

 

使用场景:开发过程中监控每一条 SQL 语句的执行时长, 已经顺便将 SQL 语句以及将参数填充到 SQL  中的 ? 号

说明: 本案例中使用的是 Mybatis 插件监控 SQL 执行时长, 使用 Mybatis-Plus 插件 进行了 SQL 参数填充

 

Mybatis 插件以 MP 插件混合使用,个人偷懒所有使用了 MP 插件,借助了MP 的一些封装,朋友们完全可以直接只用 Mybatis 插件即可,

代码比较简单,直接看代码:

/**
 * SQL 执行计划插件(仅开发环境注入使用)
 *
 * @author Alay
 * @date 2022-02-24 17:25
 */
@Intercepts(value = {
        @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})
})
public class SqlExPlanInterceptor implements Interceptor {private final Logger logger = LoggerFactory.getLogger(this.getClass());
    /**
     * 警告时长
     */
    private static final long WARNING = 500L;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 执行前时间
        long start = System.currentTimeMillis();
        Object result;
        try {
            // 调用目标函数
            result = invocation.proceed();
        } finally {
            // 执行后时间
            long end = System.currentTimeMillis();
            long duration = end - start;

            // Mapper 中的函数,借助 MP 插件中从线程变量中获取
            String mapperSqlId = SQLContextHolder.mappedId();
            // 美化后的SQL,借助 MP 插件中从线程变量中获取
            String beautifySql = SQLContextHolder.currentSql();

            if (duration >= WARNING) {
                logger.warn("Mapper 接口函数:{} \n\n{} \n\n### 执行时间:{} 毫秒,耗时太长了,请及时优化 ###", mapperSqlId, beautifySql, duration);
            } else {
                logger.info("==>Mapper 接口函数:{} \n\n{} \n\n执行时长为:{} 毫秒 <==", mapperSqlId, beautifySql, duration);
            }

            // 清除线程变量(千万要清除)
            SQLContextHolder.cleanAll();
        }
        return result;
    }
}

线程变量类:

/**
 * SQL 语句现成变量
 *
 * @author Alay
 * @date 2022-10-28 12:59
 */
public class SQLContextHolder {
    /**
     * 当前执行的 SQL
     */
    private static final ThreadLocal<String> CURRENT_SQL = new ThreadLocal<>();
    /**
     * 当前执行的 Mapper 函数
     */
    private static final ThreadLocal<String> MAPPED_ID = new ThreadLocal<>();

    public static void currentSql(String sql) {
        CURRENT_SQL.set(sql);
    }

    public static String currentSql() {
        return CURRENT_SQL.get();
    }


    public static void mappedId(String mappedId) {
        MAPPED_ID.set(mappedId);
    }

    public static String mappedId() {
        return MAPPED_ID.get();
    }

    /**
     * 清除线程变量
     */
    public static void cleanAll() {
        CURRENT_SQL.remove();
        MAPPED_ID.remove();
    }
}

使用 MP 插件模式实现的 SQL 美化器

/**
 * SQL 美化器
 *
 * @author Alay
 * @date 2022-10-28 12:57
 */
public class BeautifySqlInterceptor implements InnerInterceptor {

    /**
     * StatementHandler 执行的拦截
     */
    @Override
    public void beforePrepare(StatementHandler statementHandler, Connection connection, Integer transactionTimeout) {
        BoundSql boundSql = statementHandler.getBoundSql();
        // statementHandler 转 MetaObject
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
        // 上下文对象
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        // 将 SQL 语句进行美化,以及将参数填充到 SQL 中的 ? 号
        this.sqlHandler(mappedStatement, boundSql);
        /**
         * 如果需要改写 SQL,设置 delegate.boundSql.sql
         */
        // metaObject.setValue("delegate.boundSql.sql","新的 SQL 语句");
    }


    /**
     * SQL 拦截美化处理
     *
     * @param mappedStatement
     * @param boundSql
     */
    private void sqlHandler(MappedStatement mappedStatement, BoundSql boundSql) {
        // Mapper.java 中的方法全名
        String mapperSqlId = mappedStatement.getId();
        // 放入线程变量中
        SQLContextHolder.mappedId(mapperSqlId);

        Configuration configuration = mappedStatement.getConfiguration();

        // 解析 SQL 语句 ? 号替换为具体的值
        String beautifySql = this.parseSql(configuration, boundSql);
        // 处理过后的 SQL 放到线程变量中
        SQLContextHolder.currentSql(beautifySql);
    }


    /**
     * 解析 SQL 语句 ? 号替换为具体的值
     *
     * @param configuration
     * @param boundSql
     * @return
     */
    private String parseSql(Configuration configuration, BoundSql boundSql) {
        String sql = boundSql.getSql();
        if (null == sql || sql.length() == 0) return "";

        // SQL 美化,去除空格,换行之类的
        sql = this.beautifySql(sql);

        // SQL 中的入参对象
        Object parameterObject = boundSql.getParameterObject();
        // 参数映射关系 #{userId}
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();

        // 没有参数 (#{?})
        if (parameterMappings.size() == 0 || null == parameterObject) {
            return sql;
        }

        // typeHandler 注册器
        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();

        // 是否具备参数类型的类型处理器
        if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            // 如果是基础类型的数据,直接替换 ? 号
            sql = replaceSql(sql, parameterObject);
        } else {
            // 属性名在parameterMappings 中映射
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            for (ParameterMapping mapping : parameterMappings) {
                // #{userId}
                String property = mapping.getProperty();
                boolean hasGetter = metaObject.hasGetter(property);
                // 是否有getter 函数
                if (hasGetter) {
                    Object value = metaObject.getValue(property);
                    // SQL 参数值替换 ? 号
                    sql = replaceSql(sql, value);
                } else if (boundSql.hasAdditionalParameter(property)) {
                    // #{user.username}, 对象中取值的形式
                    Object value = boundSql.getAdditionalParameter(property);
                    // SQL 参数值替换 ? 号
                    sql = replaceSql(sql, value);
                }
            }
        }
        return sql;
    }


    private String replaceSql(String sql, Object parameter) {
        String value;
        if (parameter instanceof String) {
            // String 类型的数据需要拼接单引号 ' value '
            value = String.format("'%s'", parameter);
        } else if (parameter instanceof LocalDateTime) {
            value = String.format("'%s'", LocalDateTimeUtil.format((LocalDateTime) parameter, "yyyy-MM-dd HH:mm:ss"));
        } else if (parameter instanceof LocalDate) {
            value = String.format("'%s'", LocalDateTimeUtil.format((LocalDate) parameter, "yyyy-MM-dd"));
        } else {
            value = parameter.toString();
        }
        // 将 ? 号替换为具体值
        return sql.replaceFirst("\\?", value);
    }

    /**
     * 美化Sql
     * sql = sql.replace("\n", "").replace("\t", "").replace("  ", " ").replace("( ", "(").replace(" )", ")").replace(" ,", ",");
     */
    private String beautifySql(String sql) {
        sql = sql.replaceAll("[\\s\n ]+", " ");
        return sql;
    }
}

 

注册使用:只有开发环境才启动

这里偷懒,创建 SqlExPlanInterceptor  的同时顺手将 BeautifySqlInterceptor 添加到了 MP 的插件集合中,大家最好还是分开配置会比较规范

 /**
     * SQL 执行性能插件(只有开发环境才启动)*
     * 开启配置 @Profile(value = {"dev"}) 或者 @ConditionalOnProperty
     */
    @Bean
    @ConditionalOnProperty(value = "spring.profiles.active", havingValue = "dev")
    public SqlExPlanInterceptor sqlExPlanInterceptor(MybatisPlusInterceptor interceptor) {
        // SQL 美化插件
        interceptor.addInnerInterceptor(new BeautifySqlInterceptor());
        SqlExPlanInterceptor exPlanInterceptor = new SqlExPlanInterceptor();
        log.info("###### SQL 执行性能监控插件启动成功 ######");
        return exPlanInterceptor;
    }

 

标签:插件,String,SQL,value,boundSql,sql,Mybatis
From: https://www.cnblogs.com/Alay/p/16837762.html

相关文章

  • Mybatis 之 Mybatis-Plus 插件
    请结合上一边 >>> Mybatis插件原理<<< 进行查看Mybatis中自己定义了一个自己的插件类接口 InnerInterceptor 其内部实现了一些现成的插件,如:PaginationInn......
  • VsCode中一些可以让工作“事半功倍”的插件
    1.GitLens—Gitsupercharged这个插件可以查看代码修改的消息,比如是谁修改的以及修改时间2.Chinese(Simplified)(简体中文)简体中文,这个可以说是装的最多的一款插......
  • MyBatis
    MyBatisMybatis简介原始jdbc操作的分析原始jdbc开发存在的问题如下:数据库连接创建、释放频繁造成系统资源浪费从而影响系统性能sql语句在代码中硬编码,造成代码不易......
  • Mybatis 之 插件原理
    核心代码简介MyBatis插件原理:责任链模式  + JDK动态代理 ( 接口、代理对象、代理类:实现 jdk的 InvocationHandler)Mybatis 插件核心接口:Interceptor/***......
  • web安全学习(sql注入1)
    web安全学习(sql注入1)一.简介sql语句就是数据库语句,而sql注入就是用户将自己构造的恶意sql语句提交,然后服务器执行提交的危险语句。sql注入可能造成信息泄露以及服务器......
  • SQL的基本介绍
    1.介绍1.什么是数据库?2.什么是数据库管理系统?3.什么是SQL?1.数据库是保存有组织的数据的容器,通常是一个文件或一组文件,可以将其看作电子化的文件柜。用户可以对文件中的数......
  • MySQL——00
    索引数据结构MySQL中常见索引数据有B+Tree、Hash,其他几乎不用。Hash最简单,容易理解,其实用的也不多,因为有局限性优点:一次内存运算即可定位,效率高缺点:有可能hash冲突,这就会导......
  • SQL总结
    HiveSQL篇SerDe相关语法SerDe相关语法(多用,记清楚):#SerDe主要用于序列化和反序列化的 在Hive的建表语句中,和SerDe相关的语法为: rowformatdelimited/serde d......
  • MySQL事务
    事务通常是默认开启事务的,所以不会回滚事务保证了数据的一致性要么都成功要么都失败对于没有开启自动提交的数据,是可以回滚的,一旦提交了之后,就不可以回滚,体现了MySQ......
  • 重构 Flask 服务端项目对于 SQL 的配置使用和延迟的请求回调巧妙设计运用
    一.Flask-SQLAlchemySQLAlchemy的声明扩展是使用SQLAlchemy的最新方法,可以像Django一样在一个位置定义表和模型,然后在任何地方使用。fromsqlalchemyimportcreate_engi......