MyBatis自定义拦截器实现优化SQL日志输出

 更新时间:2026年03月16日 08:43:41   作者:汤姆yu  
这篇文章主要介绍了优化MyBatisPlus SQL日志输出的方案,针对默认日志格式存在的不足,例如缺少时间、可读性差、存储成本高等,希望对大家有所帮助

1、背景

MyBatis Plus 通过配置文件中设置 log-impl 属性来指定日志实现,以打印 SQL 语句。

mybatis-plus:
  configuration:
    log-impl:org.apache.ibatis.logging.stdout.StdOutImpl
logging:
  level:
    org.ylzl.eden.demo.mapper:DEBUG

打印出来的 SQL 内容如下:

==>  Preparing: SELECT id,login,email,activated,locked,lang_key,activation_key,reset_key,reset_date,created_by,created_date,last_modified_by,last_modified_date FROM demo_user WHERE id=?
==> Parameters: 1(Long)
<==  Columns: ID, LOGIN, EMAIL, ACTIVATED, LOCKED, LANG_KEY, ACTIVATION_KEY, RESET_KEY, RESET_DATE, CREATED_BY, CREATED_DATE, LAST_MODIFIED_BY, LAST_MODIFIED_DATE
<==  Row: 1, admin, 1813986321@qq.com, TRUE, FALSE, zh-cn, null, null, null, system, 2025-02-10 22:31:03.818, system, null
<==  Total: 1

然而,默认的日志输出格式存在以下不足:

  • 缺少日志时间,无法快速定位 SQL 执行时间。
  • SQL 语句可读性差,复杂的 SQL 语句难以阅读。
  • 日志存储成本高:SQL 模板占用较多字符,增加了日志存储成本。

2、目标

通过 MyBatis 的拦截器实现 SQL 原始语句的打印。

3、实现

首先,自定义 MyBatis 拦截器,实现 org.apache.ibatis.plugin.Interceptor 接口。

@Intercepts({
@Signature(method = "query", type = Executor.class, args= {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
 @Signature(method= "query", type = Executor.class, args= {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
 @Signature(method= "update", type = Executor.class, args= {MappedStatement.class, Object.class})
})
publicclassMybatisSqlLogInterceptorimplementsInterceptor{
privatestaticfinal Logger log = LoggerFactory.getLogger("MybatisSqlLog");
private Duration slownessThreshold = Duration.ofMillis(1000);
@Override
public Object intercept(Invocation invocation)throws Throwable {
  MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
  String mapperId = mappedStatement.getId();
  String originalSql = MybatisUtils.getSql(mappedStatement, invocation);
long start = SystemClock.now();
  Object result = invocation.proceed();
long duration = SystemClock.now() - start;
        // 当 SQL 执行超过我们设置的阈值,转为 WARN 级别 
if (Duration.ofMillis(duration).compareTo(slownessThreshold) < 0) {
   log.info("{} execute sql: {} ({} ms)", mapperId, originalSql, duration);
  } else {
   log.warn("{} execute sql took more than {} ms: {} ({} ms)", mapperId, slownessThreshold.toMillis(), originalSql, duration);
  }
return result;
 }
@Override
public Object plugin(Object target){
if (target instanceof Executor) {
   return Plugin.wrap(target, this);
  }
return target;
 }
    // 设置慢 SQL 阈值,单位为秒
publicvoidsetSlownessThreshold(Duration slownessThreshold){
this.slownessThreshold = slownessThreshold;
 }
}

笔者编写了一个工具类负责解析 MyBatis 执行语句,还原为可执行的 SQL 内容。

@UtilityClass
publicclassMybatisUtils{

    privatestaticfinal Pattern PARAMETER_PATTERN = Pattern.compile("\\?");

public String getSql(MappedStatement mappedStatement, Invocation invocation){
  Object parameter = null;
if (invocation.getArgs().length > 1) {
   parameter = invocation.getArgs()[1];
  }
  BoundSql boundSql = mappedStatement.getBoundSql(parameter);
  Configuration configuration = mappedStatement.getConfiguration();
return resolveSql(configuration, boundSql);
 }

privatestatic String resolveSql(Configuration configuration, BoundSql boundSql){
  Object parameterObject = boundSql.getParameterObject();
  List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
  String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
if (!parameterMappings.isEmpty() && parameterObject != null) {
   TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
   if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
    sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(resolveParameterValue(parameterObject)));

   } else {
    MetaObject metaObject = configuration.newMetaObject(parameterObject);
    Matcher matcher = PARAMETER_PATTERN.matcher(sql);
    StringBuffer sqlBuffer = new StringBuffer();
    for (ParameterMapping parameterMapping : parameterMappings) {
     String propertyName = parameterMapping.getProperty();
     Object obj = null;
     if (metaObject.hasGetter(propertyName)) {
      obj = metaObject.getValue(propertyName);
     } elseif (boundSql.hasAdditionalParameter(propertyName)) {
      obj = boundSql.getAdditionalParameter(propertyName);
     }
     if (matcher.find()) {
      matcher.appendReplacement(sqlBuffer, Matcher.quoteReplacement(resolveParameterValue(obj)));
     }
    }
    matcher.appendTail(sqlBuffer);
    sql = sqlBuffer.toString();
   }
  }
return sql;
 }

privatestatic String resolveParameterValue(Object obj){
if (obj instanceof CharSequence) {
   return"'" + obj + "'";
  }
if (obj instanceof Date) {
   DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
   return"'" + formatter.format(obj) + "'";
  }
return obj == null ? "" : String.valueOf(obj);
 }
}

将 MyBatis 拦截器设置为 Spring 自动装配。

@AutoConfigureAfter(DataSourceAutoConfiguration.class)
@ConditionalOnBean(SqlSessionFactory.class)
@ConditionalOnProperty(name= "mybatis.plugin.sql-log.enabled")
@EnableConfigurationProperties({MybatisPluginProperties.class})
@RequiredArgsConstructor
@Slf4j
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@Configuration(proxyBeanMethods= false)
publicclassMybatisPluginAutoConfiguration{

privatefinal MybatisPluginProperties mybatisPluginProperties;

@Bean
public MybatisSqlLogInterceptor mybatisSqlLogInterceptor(){
  MybatisSqlLogInterceptor interceptor = new MybatisSqlLogInterceptor();
  interceptor.setSlownessThreshold(mybatisPluginProperties.getSqlLog().getSlownessThreshold());
return interceptor;
 }
}

@Data
@ConfigurationProperties(prefix = "mybatis.plugin")
publicclassMybatisPluginProperties{

privatefinal SqlLog sqlLog = new SqlLog();

@Data
publicstaticclassSqlLog{

privateboolean enabled = true;

private Duration slownessThreshold = Duration.ofMillis(1000);
 }
}

当项目配置了属性 mybatis.plugin.sql-log.enabled=true 时,SQL 拦截将生效,打印的内容如下:

2024-02-10 23:03:01.845 INFO  [dev] [XNIO-1 task-1] org.ylzl.eden.demo.infrastructure.user.database.UserMapper.selectById execute sql: SELECT id,login,email,activated,locked,lang_key,activation_key,reset_key,reset_date,created_by,created_date,last_modified_by,last_modified_date FROM demo_user WHERE id=1 (10 ms)

这种日志格式比较符合我们实际的生产要求:提供日志时间、可运行的 SQL、执行耗时。

4、产出

团队引入这个组件后,在定位生产 SQL 问题时,比原来清晰多了,并且,日志文件缩减了 30% 存储成本。

到此这篇关于MyBatis自定义拦截器实现优化SQL日志输出的文章就介绍到这了,更多相关MyBatis自定义拦截器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • idea中使用git插件回滚代码的流程步骤

    idea中使用git插件回滚代码的流程步骤

    使用idea开发java代码时,如果想回滚git提交的代码, 需要操作三步,本篇步骤操作前,前提是你的电脑已经安装了git插件,并且你的idea也集成了git插件,下面是详细步骤,需要的朋友可以参考下
    2025-04-04
  • Java中HashMap如何解决哈希冲突

    Java中HashMap如何解决哈希冲突

    本文主要介绍了Java中HashMap如何解决哈希冲突,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-05-05
  • Java中的AES加密算法用法示例详解

    Java中的AES加密算法用法示例详解

    这篇文章主要介绍了Java中的AES加密算法用法的相关资料,AES是一种广泛使用的对称加密算法,支持128位、192位和256位密钥,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-01-01
  • Java开发环境不再需要配置classpath问题

    Java开发环境不再需要配置classpath问题

    这篇文章主要介绍了Java开发环境不再需要配置classpath问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-12-12
  • SpringBoot中配置nacos的方法实现

    SpringBoot中配置nacos的方法实现

    本文主要介绍了SpringBoot中配置nacos的方法实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-08-08
  • Java实现对称加密DES和AES的示例代码

    Java实现对称加密DES和AES的示例代码

    这篇文章主要介绍了如何使用Java实现采用对称密码算法的应用软件,所用算法包括DES算法和AES算法,文中的示例代码讲解详细,感兴趣的可以了解一下
    2023-04-04
  • Java实现二叉堆、大顶堆和小顶堆

    Java实现二叉堆、大顶堆和小顶堆

    二叉堆就是完全二叉树,或者是靠近完全二叉树结构的二叉树。大顶堆要求对于一个节点来说,它的左右节点都比它小;小顶堆要求对于一个节点来说,它的左右节点都比它大。本文将用Java分别实现二叉堆、大顶堆和小顶堆。需要的可以参考一下
    2022-01-01
  • 解读ResultSet的遍历方法

    解读ResultSet的遍历方法

    这篇文章主要介绍了ResultSet的遍历方法,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-02-02
  • java 连接Redis的小例子

    java 连接Redis的小例子

    这篇文章介绍了java 连接Redis的小例子,有需要的朋友可以参考一下
    2013-09-09
  • java版简单的猜数字游戏实例代码

    java版简单的猜数字游戏实例代码

    猜数字游戏是一款经典的游戏,该游戏说简单也很简单,说不简单确实也很难,那么下面这篇文章主要给大家介绍了java版简单的猜数字游戏的相关资料,文中给出了详细的实现分析和示例代码供大家参考学习,需要的朋友们下面来一起看看吧。
    2017-05-05

最新评论