You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot集成MyBatis如何记录事务关联的查询日志

事务日志关联配置方案

你当前仅开启了MyBatis Mapper层的SQL打印,Spring事务管理、JDBC连接绑定相关的日志级别被设置为WARN,且日志上下文没有事务唯一标识,因此无法将SQL语句和所属事务做关联。按以下步骤配置即可实现需求:


第一步:调整日志级别,开启事务核心日志

不需要删除原有logging.level.org.springframework=WARN配置,日志框架子包级别优先级高于父包,单独给事务相关模块开DEBUG级别即可,不会输出大量Spring框架无关日志。
在application.properties中新增以下配置:

# 保留原有Mapper层SQL日志配置
logging.level.com.example.demo.dao.UserMapper=DEBUG
logging.level.com.example.demo.dao.TransactionMapper=DEBUG

# 新增:开启Spring事务生命周期日志,可看到事务创建、参与、提交、回滚动作
logging.level.org.springframework.transaction=DEBUG
# 新增:开启JDBC连接与事务绑定日志,可看到连接和事务的对应关系
logging.level.org.springframework.jdbc.datasource=DEBUG
# 新增:Spring Boot 2.7默认使用HikariCP连接池,开启连接池日志可看到连接获取/归还与事务的关联
logging.level.com.zaxxer.hikari=DEBUG

配置完成后可看到类似Creating new transaction with name [业务方法名]、Participating in existing transaction、Committing JDBC transaction on Connection [HikariProxyConnection@xxxxxx]的日志,同一个事务会绑定唯一的Connection代理对象实例hash,后续MyBatis打印的SQL只要关联同一个Connection实例,就属于同一事务。


第二步:增加事务唯一标识(推荐,关联更直观)

靠Connection hash关联不够直观,可以通过MDC给每个事务生成全局唯一短ID,直接打印在每行日志里,一眼就能区分SQL所属事务。

  1. 编写事务ID绑定拦截器,事务开启时生成ID放入MDC,事务结束后自动清理:
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;

@Component
public class TransactionIdInterceptor implements HandlerInterceptor {
    private static final String TX_ID_MDC_KEY = "txId";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
                @Override
                public void afterBegin() {
                    // 生成8位短ID作为事务唯一标识
                    String txId = UUID.randomUUID().toString().replace("-", "").substring(0, 8);
                    MDC.put(TX_ID_MDC_KEY, txId);
                }

                @Override
                public void afterCompletion(int status) {
                    MDC.remove(TX_ID_MDC_KEY);
                }
            });
        }
        return true;
    }
}
  1. 注册拦截器到Spring MVC配置中:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Resource
    private TransactionIdInterceptor transactionIdInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(transactionIdInterceptor).addPathPatterns("/**");
    }
}
  1. 修改日志输出格式,把事务ID打印到每行日志中,在application.properties新增配置:
# 日志格式中增加%X{txId} 输出MDC中存储的事务ID
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [txId:%X{txId}] - %msg%n

配置完成后,无事务的SQL日志txId字段为空,同一事务下的所有日志(包括事务生命周期日志、SQL日志、连接池日志)都会携带相同的txId,直接通过ID即可归类同一事务的所有SQL。


第三步:PostgreSQL侧事务ID对齐(可选,用于数据库侧核对)

如果需要和数据库层面的事务ID做一一对应,可以添加MyBatis拦截器,在事务内第一次执行SQL时查询PostgreSQL内部事务ID打印到日志中:

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;

@Component
@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class PgTxIdInterceptor implements Interceptor {
    private static final Logger log = LoggerFactory.getLogger(PgTxIdInterceptor.class);
    private static final ThreadLocal<Long> PG_TX_ID_CACHE = new ThreadLocal<>();

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if (TransactionSynchronizationManager.isActualTransactionActive() && PG_TX_ID_CACHE.get() == null) {
            Executor executor = (Executor) invocation.getTarget();
            Connection conn = executor.getTransaction().getConnection();
            try (Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT txid_current()")) {
                if (rs.next()) {
                    long pgTxId = rs.getLong(1);
                    PG_TX_ID_CACHE.set(pgTxId);
                    log.info("PostgreSQL internal transaction id: {}", pgTxId);
                }
            }
        }
        try {
            return invocation.proceed();
        } finally {
            if (!TransactionSynchronizationManager.isActualTransactionActive()) {
                PG_TX_ID_CACHE.remove();
            }
        }
    }
}

配置生效后的日志示例

2024-05-20 15:22:31.123 [http-nio-8080-exec-3] DEBUG o.s.t.i.TransactionInterceptor [txId:f2d7e9a1] - Getting transaction for [com.example.demo.service.OrderService.createOrder]
2024-05-20 15:22:31.126 [http-nio-8080-exec-3] DEBUG o.s.j.d.DataSourceTransactionManager [txId:f2d7e9a1] - Creating new transaction with name [com.example.demo.service.OrderService.createOrder]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2024-05-20 15:22:31.201 [http-nio-8080-exec-3] INFO  c.e.d.config.PgTxIdInterceptor [txId:f2d7e9a1] - PostgreSQL internal transaction id: 927416
2024-05-20 15:22:31.208 [http-nio-8080-exec-3] DEBUG c.e.d.dao.UserMapper.selectById [txId:f2d7e9a1] - ==>  Preparing: SELECT id,name,balance FROM user WHERE id = ?
2024-05-20 15:22:31.210 [http-nio-8080-exec-3] DEBUG c.e.d.dao.UserMapper.selectById [txId:f2d7e9a1] - ==> Parameters: 1001(Long)
2024-05-20 15:22:31.223 [http-nio-8080-exec-3] DEBUG c.e.d.dao.OrderMapper.insert [txId:f2d7e9a1] - ==>  Preparing: INSERT INTO order (user_id,amount,create_time) VALUES (?,?,?)
2024-05-20 15:22:31.225 [http-nio-8080-exec-3] DEBUG c.e.d.dao.OrderMapper.insert [txId:f2d7e9a1] - ==> Parameters: 1001(Long), 99.00(BigDecimal), 2024-05-20 15:22:31(Timestamp)
2024-05-20 15:22:31.241 [http-nio-8080-exec-3] DEBUG c.e.d.dao.UserMapper.updateBalance [txId:f2d7e9a1] - ==>  Preparing: UPDATE user SET balance = balance - ? WHERE id = ?
2024-05-20 15:22:31.242 [http-nio-8080-exec-3] DEBUG c.e.d.dao.UserMapper.updateBalance [txId:f2d7e9a1] - ==> Parameters: 99.00(BigDecimal), 1001(Long)
2024-05-20 15:22:31.257 [http-nio-8080-exec-3] DEBUG o.s.j.d.DataSourceTransactionManager [txId:f2d7e9a1] - Initiating transaction commit
2024-05-20 15:22:31.258 [http-nio-8080-exec-3] DEBUG o.s.j.d.DataSourceTransactionManager [txId:f2d7e9a1] - Committing JDBC transaction on Connection [HikariProxyConnection@1827364 wrapping org.postgresql.jdbc.PgConnection@7a2d9f3]

所有携带相同txId:f2d7e9a1的SQL都属于同一事务,可直接归类。


内容的提问来源于stack exchange,提问作者Majid Abdolhosseini

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 18:57:24