请结合上一篇 >>> 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