Spring事务失效的十大陷阱与终极解决方案

本文将全面剖析Spring事务失效的各种场景,提供完整的代码示例和解决方案,帮你彻底掌握事务管理的精髓。

1. 事务管理基础:Spring事务工作原理深度解析

在深入问题之前,我们先完整理解Spring声明式事务的工作机制:

1.1 Spring事务架构概览

java 复制代码
// Spring事务管理的核心组件关系
@Component
public class TransactionArchitecture {
    /*
     * 事务管理核心流程:
     * 1. 解析@Transactional注解
     * 2. 创建AOP代理
     * 3. 事务拦截器处理
     * 4. 事务管理器协调
     * 5. 连接资源管理
     */
}

// 事务拦截器核心逻辑(简化版)
public class TransactionInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        // 1. 获取事务属性
        TransactionAttribute txAttr = getTransactionAttributeSource()
            .getTransactionAttribute(invocation.getMethod(), invocation.getClass());
        
        // 2. 创建或加入事务
        TransactionStatus status = transactionManager.getTransaction(txAttr);
        
        try {
            // 3. 执行业务方法
            Object result = invocation.proceed();
            
            // 4. 提交事务
            transactionManager.commit(status);
            return result;
        } catch (Exception ex) {
            // 5. 回滚处理
            completeTransactionAfterThrowing(txAttr, status, ex);
            throw ex;
        }
    }
}

1.2 完整的事务配置示例

java 复制代码
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
    
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        DataSourceTransactionManager transactionManager = 
            new DataSourceTransactionManager(dataSource);
        
        // 完整的事务管理器配置
        transactionManager.setNestedTransactionAllowed(true);
        transactionManager.setRollbackOnCommitFailure(false);
        transactionManager.setDefaultTimeout(30); // 30秒超时
        
        return transactionManager;
    }
    
    @Bean
    public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) {
        TransactionTemplate template = new TransactionTemplate(transactionManager);
        template.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
        template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        template.setTimeout(30);
        return template;
    }
}

2. 陷阱一:同类方法调用(最常见的坑)

2.1 问题完整复现

java 复制代码
@Service
@Slf4j
public class OrderService {
    
    @Autowired
    private OrderRepository orderRepository;
    
    @Autowired
    private InventoryService inventoryService;
    
    /**
     * 创建订单的主方法
     */
    public OrderCreateResult createOrder(OrderCreateRequest request) {
        log.info("开始创建订单: {}", request.getOrderNo());
        
        // 1. 验证订单
        validateOrder(request);
        
        // 2. 保存订单(期望在事务中)
        Order order = saveOrder(request);  // ❌ 事务失效点
        
        // 3. 更新库存
        updateInventory(order);
        
        // 4. 记录日志
        logOrderCreate(order);
        
        return new OrderCreateResult(true, "创建成功", order.getId());
    }
    
    /**
     * 保存订单数据 - 期望在事务中执行
     */
    @Transactional
    public Order saveOrder(OrderCreateRequest request) {
        log.info("保存订单数据");
        
        Order order = convertToOrder(request);
        order.setStatus(OrderStatus.CREATED);
        
        // 保存订单主表
        Order savedOrder = orderRepository.save(order);
        
        // 保存订单明细
        saveOrderItems(request.getItems(), savedOrder.getId());
        
        // 模拟一个业务异常
        if (request.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
            throw new BusinessException("订单金额必须大于0");
        }
        
        return savedOrder;
    }
    
    /**
     * 保存订单明细
     */
    @Transactional(propagation = Propagation.REQUIRED)
    private void saveOrderItems(List<OrderItem> items, Long orderId) {
        for (OrderItem item : items) {
            item.setOrderId(orderId);
            orderRepository.saveItem(item);
        }
    }
    
    /**
     * 更新库存
     */
    private void updateInventory(Order order) {
        inventoryService.deductInventory(order);
    }
    
    /**
     * 记录订单创建日志
     */
    private void logOrderCreate(Order order) {
        // 记录操作日志
        System.out.println("订单创建完成: " + order.getId());
    }
}

2.2 问题深度分析

java 复制代码
/**
 * 问题分析服务 - 通过AOP原理解释为什么事务失效
 */
@Component
@Aspect
@Slf4j
public class TransactionAnalysisAspect {
    
    /**
     * 模拟Spring AOP代理的工作方式
     */
    public void demonstrateProxyMechanism() {
        /*
         * 实际Spring创建的代理对象结构:
         * 
         * ProxyOrderService (Spring AOP代理)
         *   - target: RealOrderService (原始对象)
         *   - advisors: [TransactionInterceptor]
         * 
         * 当调用 proxy.saveOrder() 时:
         *   1. 代理拦截方法调用
         *   2. 执行TransactionInterceptor
         *   3. 开启事务
         *   4. 调用target.saveOrder()
         *   5. 提交/回滚事务
         * 
         * 当在同一个类中调用 this.saveOrder() 时:
         *   1. 直接调用原始对象的方法
         *   2. 绕过代理拦截器
         *   3. 事务注解失效
         */
    }
    
    @Around("@annotation(transactional)")
    public Object analyzeTransaction(ProceedingJoinPoint joinPoint, 
                                   Transactional transactional) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        boolean isTransactionActive = TransactionSynchronizationManager.isActualTransactionActive();
        
        log.info("方法 {} 调用前,事务状态: {}", methodName, isTransactionActive);
        
        Object result = joinPoint.proceed();
        
        isTransactionActive = TransactionSynchronizationManager.isActualTransactionActive();
        log.info("方法 {} 调用后,事务状态: {}", methodName, isTransactionActive);
        
        return result;
    }
}

2.3 完整解决方案

java 复制代码
@Service
@Slf4j
public class CorrectOrderService {
    
    @Autowired
    private OrderRepository orderRepository;
    
    @Autowired
    private InventoryService inventoryService;
    
    @Autowired
    private ApplicationContext applicationContext;
    
    /**
     * 方案1:通过ApplicationContext获取代理对象
     */
    public OrderCreateResult createOrderV1(OrderCreateRequest request) {
        validateOrder(request);
        
        // 获取代理对象
        CorrectOrderService proxy = applicationContext.getBean(CorrectOrderService.class);
        Order order = proxy.saveOrder(request);  // ✅ 通过代理调用
        
        updateInventory(order);
        logOrderCreate(order);
        
        return new OrderCreateResult(true, "创建成功", order.getId());
    }
    
    /**
     * 方案2:自我注入代理对象
     */
    @Autowired
    @Lazy  // 避免循环依赖问题
    private CorrectOrderService self;
    
    public OrderCreateResult createOrderV2(OrderCreateRequest request) {
        validateOrder(request);
        
        Order order = self.saveOrder(request);  // ✅ 通过注入的代理调用
        
        updateInventory(order);
        logOrderCreate(order);
        
        return new OrderCreateResult(true, "创建成功", order.getId());
    }
    
    /**
     * 方案3:重构方法结构,事务方法作为入口
     */
    @Transactional
    public OrderCreateResult createOrderV3(OrderCreateRequest request) {
        validateOrder(request);
        
        Order order = saveOrderInternal(request);  // ✅ 在事务方法内调用
        
        updateInventory(order);
        logOrderCreate(order);
        
        return new OrderCreateResult(true, "创建成功", order.getId());
    }
    
    /**
     * 方案4:使用编程式事务
     */
    @Autowired
    private TransactionTemplate transactionTemplate;
    
    public OrderCreateResult createOrderV4(OrderCreateRequest request) {
        validateOrder(request);
        
        Order order = transactionTemplate.execute(status -> {
            try {
                return saveOrderInternal(request);
            } catch (Exception e) {
                status.setRollbackOnly();
                throw e;
            }
        });
        
        updateInventory(order);
        logOrderCreate(order);
        
        return new OrderCreateResult(true, "创建成功", order.getId());
    }
    
    @Transactional
    public Order saveOrder(OrderCreateRequest request) {
        return saveOrderInternal(request);
    }
    
    /**
     * 内部保存逻辑 - 不添加事务注解
     */
    private Order saveOrderInternal(OrderCreateRequest request) {
        log.info("保存订单数据");
        
        Order order = convertToOrder(request);
        order.setStatus(OrderStatus.CREATED);
        
        Order savedOrder = orderRepository.save(order);
        saveOrderItems(request.getItems(), savedOrder.getId());
        
        if (request.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
            throw new BusinessException("订单金额必须大于0");
        }
        
        return savedOrder;
    }
    
    // 其他辅助方法...
    private void saveOrderItems(List<OrderItem> items, Long orderId) {
        for (OrderItem item : items) {
            item.setOrderId(orderId);
            orderRepository.saveItem(item);
        }
    }
    
    private void validateOrder(OrderCreateRequest request) {
        // 验证逻辑
    }
    
    private void updateInventory(Order order) {
        inventoryService.deductInventory(order);
    }
    
    private void logOrderCreate(Order order) {
        // 日志记录
    }
}

3. 陷阱二:异常处理不当

3.1 异常处理完整示例

java 复制代码
@Service
@Slf4j
public class ExceptionHandlingService {
    
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private EmailService emailService;
    
    /**
     * 场景1:捕获异常导致事务不回滚
     */
    @Transactional
    public void registerUserV1(User user) {
        try {
            // 保存用户
            userRepository.save(user);
            
            // 发送欢迎邮件(可能抛出异常)
            emailService.sendWelcomeEmail(user.getEmail());
            
        } catch (Exception e) {
            // ❌ 捕获了所有异常,事务不会回滚
            log.error("用户注册失败: {}", user.getUsername(), e);
            // 用户记录已保存,但邮件发送失败,数据不一致
        }
    }
    
    /**
     * 场景2:检查异常默认不回滚
     */
    @Transactional
    public void uploadUserAvatarV1(User user, InputStream avatarStream) throws IOException {
        userRepository.update(user);
        
        // 上传头像(抛出IOException)
        fileService.uploadAvatar(user.getId(), avatarStream);  // ❌ IOException不会触发回滚
        
        // 如果上传失败,用户信息已更新,但头像缺失
    }
    
    /**
     * 场景3:错误的异常类型指定
     */
    @Transactional(rollbackFor = RuntimeException.class) // 默认值,但业务异常可能继承Exception
    public void processBusinessV1(BusinessRequest request) {
        userRepository.updateStatus(request.getUserId(), "PROCESSING");
        
        // 业务处理,抛出自定义业务异常
        businessService.process(request);  // ❌ 如果BusinessException继承Exception,不会回滚
        
        userRepository.updateStatus(request.getUserId(), "COMPLETED");
    }
    
    /**
     * 正确解决方案
     */
    
    /**
     * 方案1:正确配置回滚异常
     */
    @Transactional(rollbackFor = Exception.class) // ✅ 所有异常都回滚
    public void registerUserV2(User user) {
        try {
            userRepository.save(user);
            emailService.sendWelcomeEmail(user.getEmail());
            
        } catch (EmailException e) {
            // 只捕获邮件发送异常,不影响事务
            log.warn("欢迎邮件发送失败: {}", user.getEmail(), e);
            // 邮件发送失败不影响用户注册,事务继续提交
            
        } catch (Exception e) {
            // 其他异常重新抛出,触发回滚
            log.error("用户注册失败: {}", user.getUsername(), e);
            throw new BusinessException("用户注册失败", e);
        }
    }
    
    /**
     * 方案2:分层异常处理
     */
    @Transactional(rollbackFor = BusinessException.class)
    public void uploadUserAvatarV2(User user, InputStream avatarStream) {
        try {
            userRepository.update(user);
            fileService.uploadAvatar(user.getId(), avatarStream);
            
        } catch (IOException e) {
            // 将检查异常转换为运行时异常
            throw new BusinessException("头像上传失败", e);
        }
    }
    
    /**
     * 方案3:编程式异常控制
     */
    @Transactional
    public void processBusinessV2(BusinessRequest request) {
        TransactionStatus status = TransactionAspectSupport.currentTransactionStatus();
        
        try {
            userRepository.updateStatus(request.getUserId(), "PROCESSING");
            businessService.process(request);
            userRepository.updateStatus(request.getUserId(), "COMPLETED");
            
        } catch (BusinessException e) {
            // 手动标记回滚
            status.setRollbackOnly();
            log.error("业务处理失败: {}", request.getRequestId(), e);
            throw e;
        }
    }
    
    /**
     * 方案4:自定义异常回滚策略
     */
    @Component
    public class CustomRollbackPolicy {
        
        @Autowired
        private PlatformTransactionManager transactionManager;
        
        public void executeWithRollbackPolicy(Runnable businessLogic, 
                                            Class<? extends Exception>... rollbackExceptions) {
            DefaultTransactionAttribute transactionAttribute = new DefaultTransactionAttribute();
            transactionAttribute.setRollbackRules(Arrays.asList(rollbackExceptions));
            
            TransactionStatus status = transactionManager.getTransaction(transactionAttribute);
            
            try {
                businessLogic.run();
                transactionManager.commit(status);
            } catch (Exception e) {
                transactionManager.rollback(status);
                throw e;
            }
        }
    }
}

4. 陷阱三:事务传播机制误用

4.1 传播机制完整示例

java 复制代码
@Service
@Slf4j
public class PropagationService {
    
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private OrderRepository orderRepository;
    
    @Autowired
    private LogRepository logRepository;
    
    @Autowired
    private PropagationService self;
    
    /**
     * 错误示例:错误使用REQUIRED传播
     */
    @Transactional
    public void processBatchOrdersV1(List<Order> orders) {
        int successCount = 0;
        int failureCount = 0;
        
        for (Order order : orders) {
            try {
                // ❌ 每个订单处理在同一个事务中
                processSingleOrder(order);
                successCount++;
                
            } catch (Exception e) {
                failureCount++;
                log.error("订单处理失败: {}", order.getOrderNo(), e);
                // 一个订单失败会导致整个批次回滚
            }
        }
        
        log.info("批次处理完成: 成功={}, 失败={}", successCount, failureCount);
    }
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void processSingleOrder(Order order) {
        // 订单处理逻辑
        orderRepository.save(order);
        inventoryService.deductStock(order);
        
        if (order.getAmount().compareTo(BigDecimal.valueOf(10000)) > 0) {
            throw new BusinessException("订单金额超限");
        }
    }
    
    /**
     * 正确使用传播机制
     */
    
    /**
     * 方案1:使用REQUIRES_NEW实现事务隔离
     */
    public BatchProcessResult processBatchOrdersV2(List<Order> orders) {
        BatchProcessResult result = new BatchProcessResult();
        
        for (Order order : orders) {
            try {
                // ✅ 每个订单在独立事务中处理
                self.processSingleOrderInNewTx(order);
                result.incrementSuccess();
                
            } catch (Exception e) {
                result.incrementFailure();
                log.error("订单处理失败: {}", order.getOrderNo(), e);
                // 单个订单失败不影响其他订单
            }
        }
        
        // 记录批次日志(在独立事务中)
        self.logBatchResult(result);
        
        return result;
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void processSingleOrderInNewTx(Order order) {
        orderRepository.save(order);
        inventoryService.deductStock(order);
        
        if (order.getAmount().compareTo(BigDecimal.valueOf(10000)) > 0) {
            throw new BusinessException("订单金额超限");
        }
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logBatchResult(BatchProcessResult result) {
        logRepository.saveBatchLog(result);
    }
    
    /**
     * 方案2:使用NESTED传播(需要支持保存点)
     */
    @Transactional
    public void processOrderWithNestedOperations(Order order) {
        // 主订单处理
        orderRepository.save(order);
        
        try {
            // ✅ 嵌套事务:可以独立回滚
            self.processOrderItems(order.getItems());
        } catch (Exception e) {
            log.error("订单明细处理失败,继续处理其他逻辑", e);
            // 明细处理失败不影响主订单
        }
        
        // 继续处理其他逻辑
        updateOrderStatistics(order);
    }
    
    @Transactional(propagation = Propagation.NESTED)
    public void processOrderItems(List<OrderItem> items) {
        for (OrderItem item : items) {
            itemRepository.save(item);
            
            if (item.getQuantity() <= 0) {
                throw new BusinessException("商品数量必须大于0");
            }
        }
    }
    
    /**
     * 方案3:使用NOT_SUPPORTED暂停事务
     */
    @Transactional
    public void generateReportWithHeavyOperation() {
        // 轻量级数据库操作
        List<ReportData> data = reportRepository.getReportData();
        
        // ✅ 暂停事务,执行重量级操作
        String reportFile = self.generateLargeReportFile(data);
        
        // 恢复事务,保存报告记录
        reportRepository.saveReportRecord(reportFile);
    }
    
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public String generateLargeReportFile(List<ReportData> data) {
        // 生成大型报告文件(耗时操作)
        // 不在事务中,避免长事务和锁竞争
        return reportService.generateExcelReport(data);
    }
    
    /**
     * 传播机制使用指南
     */
    public class PropagationGuide {
        /*
         * REQUIRED(默认):如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务
         * 适用场景:大多数业务方法
         * 
         * REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起
         * 适用场景:需要独立提交/回滚的子任务
         * 
         * NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则创建一个新的事务
         * 适用场景:可部分回滚的子操作
         * 
         * SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行
         * 适用场景:查询方法,可有可无事务
         * 
         * NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起
         * 适用场景:非事务性操作,如文件处理、远程调用
         * 
         * NEVER:以非事务方式运行,如果当前存在事务,则抛出异常
         * 适用场景:强制非事务环境
         * 
         * MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常
         * 适用场景:强制要求事务环境
         */
    }
}

5. 陷阱四:访问权限限制

5.1 访问权限完整示例

java 复制代码
@Service
@Slf4j
public class AccessControlService {
    
    /**
     * 错误示例:非public方法使用事务注解
     */
    @Transactional
    void internalProcessV1() {  // ❌ package-private方法
        // 事务不会生效
        repository.updateData();
    }
    
    @Transactional
    protected void protectedProcessV1() {  // ❌ protected方法  
        // 事务不会生效
        repository.updateData();
    }
    
    @Transactional
    private void privateProcessV1() {  // ❌ private方法
        // 事务不会生效
        repository.updateData();
    }
    
    /**
     * 解决方案
     */
    
    /**
     * 方案1:正确的访问权限
     */
    @Transactional
    public void publicProcess() {  // ✅ public方法
        repository.updateData();
    }
    
    /**
     * 方案2:门面模式 + 内部方法调用
     */
    public void complexBusinessProcess() {
        // 步骤1:非事务性预处理
        preProcess();
        
        // 步骤2:事务性核心处理
        transactionalCoreProcess();
        
        // 步骤3:非事务性后处理
        postProcess();
    }
    
    @Transactional
    public void transactionalCoreProcess() {
        // 核心业务逻辑
        step1();
        step2();
        step3();
    }
    
    // 内部方法 - 不添加事务注解
    private void preProcess() {
        // 数据验证、参数校验等
    }
    
    private void step1() {
        // 步骤1逻辑
    }
    
    private void step2() {
        // 步骤2逻辑
    }
    
    private void step3() {
        // 步骤3逻辑
    }
    
    private void postProcess() {
        // 清理、通知等
    }
    
    /**
     * 方案3:使用AspectJ模式(需要额外配置)
     */
    @Configuration
    @EnableTransactionManagement(mode = AdviceMode.ASPECTJ)  // 使用AspectJ代理
    public class AspectJTransactionConfig {
        // AspectJ模式下,非public方法的事务注解也会生效
        // 但需要编译时或加载时织入
    }
    
    @Transactional
    protected void aspectJProtectedProcess() {
        // 在AspectJ模式下,protected方法的事务会生效
        repository.updateData();
    }
}

6. 陷阱五:数据库配置问题

6.1 数据库配置完整示例

java 复制代码
@Service
@Slf4j
public class DatabaseConfigService {
    
    /**
     * 错误示例:使用不支持事务的存储引擎
     */
    @Entity
    @Table(name = "operation_logs")
    public class OperationLog {
        // 如果数据库表使用MyISAM引擎,事务将失效
    }
    
    @Transactional
    public void batchInsertLogsV1(List<OperationLog> logs) {
        for (OperationLog log : logs) {
            logRepository.save(log);  // ❌ MyISAM不支持事务
        }
        // 即使发生异常,已插入的数据也不会回滚
    }
    
    /**
     * 解决方案
     */
    
    /**
     * 方案1:确保使用InnoDB引擎
     */
    @Entity
    @Table(name = "operation_logs", 
           options = "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4")
    public class OperationLog {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
        
        private String content;
        
        @CreationTimestamp
        private LocalDateTime createTime;
    }
    
    /**
     * 方案2:数据库表DDL验证
     */
    @Component
    public class TableEngineValidator {
        
        @Autowired
        private JdbcTemplate jdbcTemplate;
        
        @PostConstruct
        public void validateTableEngines() {
            String sql = "SELECT TABLE_NAME, ENGINE FROM information_schema.TABLES " +
                        "WHERE TABLE_SCHEMA = DATABASE() AND ENGINE != 'InnoDB'";
            
            List<Map<String, Object>> results = jdbcTemplate.queryForList(sql);
            
            if (!results.isEmpty()) {
                log.warn("发现非InnoDB引擎的表: {}", results);
                // 可以抛出异常或记录警告
            }
        }
    }
    
    /**
     * 方案3:编程式事务 + 手动回滚补偿
     */
    @Service
    public class CompensationService {
        
        @Autowired
        private JdbcTemplate jdbcTemplate;
        
        public void batchInsertWithCompensation(List<OperationLog> logs) {
            List<Long> insertedIds = new ArrayList<>();
            
            try {
                for (OperationLog log : logs) {
                    // 手动插入并记录ID
                    Long id = insertLogAndReturnId(log);
                    insertedIds.add(id);
                }
                
                // 业务验证
                validateBusinessRules(logs);
                
            } catch (Exception e) {
                // 手动回滚:删除已插入的记录
                rollbackInsertedLogs(insertedIds);
                throw new BusinessException("批量插入失败,已回滚", e);
            }
        }
        
        private Long insertLogAndReturnId(OperationLog log) {
            KeyHolder keyHolder = new GeneratedKeyHolder();
            
            jdbcTemplate.update(connection -> {
                PreparedStatement ps = connection.prepareStatement(
                    "INSERT INTO operation_logs (content) VALUES (?)", 
                    Statement.RETURN_GENERATED_KEYS
                );
                ps.setString(1, log.getContent());
                return ps;
            }, keyHolder);
            
            return keyHolder.getKey().longValue();
        }
        
        private void rollbackInsertedLogs(List<Long> ids) {
            if (!ids.isEmpty()) {
                String deleteSql = "DELETE FROM operation_logs WHERE id IN (" +
                    ids.stream().map(String::valueOf).collect(Collectors.joining(",")) + ")";
                jdbcTemplate.update(deleteSql);
            }
        }
    }
}

7. 陷阱六:连接池配置问题

7.1 连接池配置完整示例

java 复制代码
@Configuration
public class DataSourceConfig {
    
    /**
     * 错误示例:自动提交设置冲突
     */
    @Bean
    public DataSource wrongDataSource() {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setUsername("root");
        dataSource.setPassword("password");
        
        // ❌ 连接池自动提交与事务管理冲突
        dataSource.setAutoCommit(true);
        
        return dataSource;
    }
    
    /**
     * 正确配置
     */
    @Bean
    @Primary
    public DataSource dataSource() {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setUsername("root");
        dataSource.setPassword("password");
        
        // ✅ 正确的连接池配置
        dataSource.setAutoCommit(false);  // 让Spring管理事务提交
        dataSource.setConnectionTimeout(30000);
        dataSource.setIdleTimeout(600000);
        dataSource.setMaxLifetime(1800000);
        dataSource.setMaximumPoolSize(20);
        dataSource.setMinimumIdle(5);
        
        // 事务相关配置
        dataSource.setTransactionIsolation("TRANSACTION_READ_COMMITTED");
        dataSource.setLeakDetectionThreshold(60000);
        
        return dataSource;
    }
    
    /**
     * 连接池监控
     */
    @Component
    public class DataSourceMonitor {
        
        @Autowired
        private DataSource dataSource;
        
        @Scheduled(fixedRate = 30000)  // 每30秒监控一次
        public void monitorDataSource() {
            if (dataSource instanceof HikariDataSource) {
                HikariDataSource hikariDataSource = (HikariDataSource) dataSource;
                HikariPoolMXBean pool = hikariDataSource.getHikariPoolMXBean();
                
                log.info("连接池状态: 活跃={}, 空闲={}, 等待={}, 总数={}", 
                        pool.getActiveConnections(),
                        pool.getIdleConnections(),
                        pool.getThreadsAwaitingConnection(),
                        pool.getTotalConnections());
                
                // 检查连接泄漏
                if (pool.getActiveConnections() > 15) {
                    log.warn("活跃连接数过高,可能存在连接泄漏");
                }
            }
        }
    }
}

8. 陷阱七:多数据源事务混淆

8.1 多数据源完整配置

java 复制代码
@Configuration
public class MultiDataSourceConfig {
    
    /**
     * 主数据源
     */
    @Bean
    @Primary
    @ConfigurationProperties("spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }
    
    /**
     * 从数据源
     */
    @Bean
    @ConfigurationProperties("spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }
    
    /**
     * 主数据源事务管理器
     */
    @Bean
    @Primary
    public PlatformTransactionManager primaryTransactionManager() {
        return new DataSourceTransactionManager(primaryDataSource());
    }
    
    /**
     * 从数据源事务管理器
     */
    @Bean
    public PlatformTransactionManager secondaryTransactionManager() {
        return new DataSourceTransactionManager(secondaryDataSource());
    }
    
    /**
     * JPA实体管理器工厂
     */
    @Bean
    @Primary
    public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(
            EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(primaryDataSource())
                .packages("com.example.primary.entity")
                .persistenceUnit("primary")
                .build();
    }
    
    @Bean
    public LocalContainerEntityManagerFactoryBean secondaryEntityManagerFactory(
            EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(secondaryDataSource())
                .packages("com.example.secondary.entity")
                .persistenceUnit("secondary")
                .build();
    }
}

/**
 * 多数据源服务示例
 */
@Service
@Slf4j
public class MultiDataSourceService {
    
    @Autowired
    @Qualifier("primaryTransactionManager")
    private PlatformTransactionManager primaryTxManager;
    
    @Autowired
    @Qualifier("secondaryTransactionManager") 
    private PlatformTransactionManager secondaryTxManager;
    
    @Autowired
    private PrimaryRepository primaryRepository;
    
    @Autowired
    private SecondaryRepository secondaryRepository;
    
    /**
     * 错误示例:错误的事务管理器使用
     */
    @Transactional  // ❌ 使用默认事务管理器,只能管理主数据源
    public void crossDataSourceOperationV1(CrossDataRequest request) {
        // 操作主数据源
        primaryRepository.save(request.getPrimaryData());
        
        // 操作从数据源 - 不在事务管理中!
        secondaryRepository.save(request.getSecondaryData());
        
        // 如果这里发生异常,主数据源会回滚,但从数据源不会
    }
    
    /**
     * 解决方案1:分别使用不同的事务管理器
     */
    public void crossDataSourceOperationV2(CrossDataRequest request) {
        // 主数据源操作
        primaryTxManager.execute(status -> {
            primaryRepository.save(request.getPrimaryData());
            return null;
        });
        
        // 从数据源操作  
        secondaryTxManager.execute(status -> {
            secondaryRepository.save(request.getSecondaryData());
            return null;
        });
        
        // 每个操作在独立事务中,但无法保证原子性
    }
    
    /**
     * 解决方案2:使用分布式事务(如Atomikos)
     */
    @Configuration
    @EnableTransactionManagement
    public static class JtaTransactionConfig {
        
        @Bean
        public JtaTransactionManager transactionManager() {
            UserTransactionManager userTransactionManager = new UserTransactionManager();
            UserTransaction userTransaction = new UserTransactionImp();
            return new JtaTransactionManager(userTransaction, userTransactionManager);
        }
    }
    
    @Transactional  // 使用JTA事务管理器
    public void crossDataSourceOperationV3(CrossDataRequest request) {
        // 在分布式事务中,两个数据源的操作会一起提交或回滚
        primaryRepository.save(request.getPrimaryData());
        secondaryRepository.save(request.getSecondaryData());
    }
    
    /**
     * 解决方案3:最终一致性模式
     */
    public void crossDataSourceOperationV4(CrossDataRequest request) {
        // 步骤1:主数据源操作
        Long primaryId = savePrimaryData(request.getPrimaryData());
        
        try {
            // 步骤2:从数据源操作
            saveSecondaryData(request.getSecondaryData(), primaryId);
            
            // 步骤3:标记主数据完成
            markPrimaryDataCompleted(primaryId);
            
        } catch (Exception e) {
            // 步骤4:补偿操作
            compensatePrimaryData(primaryId);
            throw new BusinessException("跨数据源操作失败", e);
        }
    }
    
    @Transactional("primaryTransactionManager")
    public Long savePrimaryData(PrimaryData data) {
        PrimaryData saved = primaryRepository.save(data);
        return saved.getId();
    }
    
    @Transactional("secondaryTransactionManager")
    public void saveSecondaryData(SecondaryData data, Long primaryId) {
        data.setPrimaryId(primaryId);
        secondaryRepository.save(data);
    }
    
    @Transactional("primaryTransactionManager")
    public void markPrimaryDataCompleted(Long primaryId) {
        primaryRepository.updateStatus(primaryId, "COMPLETED");
    }
    
    @Transactional("primaryTransactionManager")
    public void compensatePrimaryData(Long primaryId) {
        primaryRepository.updateStatus(primaryId, "FAILED");
        // 可以记录补偿日志等
    }
}

9. 陷阱八:异步执行事务丢失

9.1 异步事务完整示例

java 复制代码
@Service
@Slf4j
@EnableAsync
public class AsyncTransactionService {
    
    @Autowired
    private OrderRepository orderRepository;
    
    @Autowired
    private EmailService emailService;
    
    @Autowired
    private AsyncTransactionService self;
    
    /**
     * 错误示例:异步方法中的事务注解
     */
    @Transactional
    @Async
    public void asyncProcessOrderV1(Order order) {
        // ❌ 异步方法中的事务注解通常不会按预期工作
        orderRepository.updateStatus(order.getId(), "PROCESSING");
        
        // 复杂的业务处理
        processComplexBusiness(order);
        
        orderRepository.updateStatus(order.getId(), "COMPLETED");
        // 事务上下文可能丢失,回滚机制不可靠
    }
    
    /**
     * 解决方案1:同步处理核心事务,异步处理非核心操作
     */
    @Transactional
    public void processOrderV2(Order order) {
        // 步骤1:同步处理核心业务(在事务中)
        orderRepository.updateStatus(order.getId(), "PROCESSING");
        processCoreBusiness(order);
        orderRepository.updateStatus(order.getId(), "COMPLETED");
        
        // 步骤2:异步处理非核心操作(不在事务中)
        self.asyncSendNotifications(order);
    }
    
    @Async
    public void asyncSendNotifications(Order order) {
        try {
            // 发送邮件通知
            emailService.sendOrderCompleteEmail(order);
            
            // 发送短信通知
            smsService.sendOrderCompleteSms(order);
            
            // 其他非核心操作
            logService.recordOperationLog(order);
            
        } catch (Exception e) {
            // 异步操作失败不影响主流程
            log.error("异步通知发送失败: {}", order.getId(), e);
        }
    }
    
    /**
     * 解决方案2:使用@TransactionalEventListener
     */
    @Transactional
    public void processOrderV3(Order order) {
        // 核心业务处理
        orderRepository.updateStatus(order.getId(), "PROCESSING");
        processCoreBusiness(order);
        orderRepository.updateStatus(order.getId(), "COMPLETED");
        
        // 发布事务事件
        applicationEventPublisher.publishEvent(new OrderCompletedEvent(this, order));
    }
    
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    @Async
    public void handleOrderCompletedEvent(OrderCompletedEvent event) {
        // 只有在主事务提交后才会执行
        Order order = event.getOrder();
        
        try {
            emailService.sendOrderCompleteEmail(order);
            smsService.sendOrderCompleteSms(order);
        } catch (Exception e) {
            log.error("订单完成后续处理失败: {}", order.getId(), e);
        }
    }
    
    /**
     * 解决方案3:编程式事务 + 异步执行
     */
    @Autowired
    private TransactionTemplate transactionTemplate;
    
    @Async
    public void asyncProcessWithProgrammaticTx(Order order) {
        // 在异步线程中创建新事务
        transactionTemplate.execute(status -> {
            try {
                orderRepository.updateStatus(order.getId(), "PROCESSING");
                processCoreBusiness(order);
                orderRepository.updateStatus(order.getId(), "COMPLETED");
                return null;
            } catch (Exception e) {
                status.setRollbackOnly();
                throw e;
            }
        });
    }
    
    /**
     * 异步配置
     */
    @Configuration
    @EnableAsync
    public static class AsyncConfig implements AsyncConfigurer {
        
        @Override
        public Executor getAsyncExecutor() {
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            executor.setCorePoolSize(10);
            executor.setMaxPoolSize(50);
            executor.setQueueCapacity(100);
            executor.setThreadNamePrefix("AsyncTx-");
            executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
            executor.setWaitForTasksToCompleteOnShutdown(true);
            executor.setAwaitTerminationSeconds(60);
            executor.initialize();
            return executor;
        }
        
        @Override
        public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
            return (ex, method, params) -> {
                log.error("异步方法执行异常: {}.{}", method.getDeclaringClass().getName(), method.getName(), ex);
            };
        }
    }
}

10. 陷阱九:事务超时与只读设置

10.1 超时与只读配置完整示例

java 复制代码
@Service
@Slf4j
public class TimeoutReadonlyService {
    
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private OrderRepository orderRepository;
    
    /**
     * 错误示例:长事务无超时设置
     */
    @Transactional
    public void generateComplexReportV1(ReportRequest request) {
        // 步骤1:查询大量数据
        List<User> users = userRepository.findAllActiveUsers();  // 可能很慢
        
        // 步骤2:复杂计算
        processComplexCalculation(users);  // 可能很慢
        
        // 步骤3:生成报告
        generateReportFile(users);  // 可能很慢
        
        // ❌ 没有超时设置,可能长时间占用数据库连接
    }
    
    /**
     * 错误示例:写操作使用只读事务
     */
    @Transactional(readOnly = true)  // ❌ 只读事务
    public void updateUserStatisticsV1() {
        // 尝试在只读事务中执行写操作
        userRepository.updateStatistics();  // 可能失败或行为异常
    }
    
    /**
     * 正确配置示例
     */
    
    /**
     * 方案1:合理设置超时时间
     */
    @Transactional(timeout = 30)  // ✅ 30秒超时
    public void generateComplexReportV2(ReportRequest request) {
        try {
            List<User> users = userRepository.findAllActiveUsers();
            processComplexCalculation(users);
            generateReportFile(users);
            
        } catch (TransactionTimedOutException e) {
            log.error("报告生成超时: {}", request, e);
            throw new BusinessException("报告生成超时,请重试");
        }
    }
    
    /**
     * 方案2:只读事务用于查询操作
     */
    @Transactional(readOnly = true, timeout = 10)
    public List<UserReport> getUserReports() {
        // 纯查询操作,使用只读事务
        return userRepository.generateUserReports();
    }
    
    /**
     * 方案3:拆分长事务
     */
    public void generateComplexReportV3(ReportRequest request) {
        // 步骤1:快速查询必要数据
        List<Long> userIds = userRepository.findActiveUserIds();
        
        // 步骤2:分批处理
        Lists.partition(userIds, 1000).forEach(batch -> {
            processUserBatch(batch, request);
        });
    }
    
    @Transactional(timeout = 10)
    public void processUserBatch(List<Long> userIds, ReportRequest request) {
        List<User> users = userRepository.findByIdIn(userIds);
        processBatchCalculation(users);
        // 每个批次在独立短事务中处理
    }
    
    /**
     * 方案4:动态超时配置
     */
    @Autowired
    private TransactionTemplate transactionTemplate;
    
    public void processWithDynamicTimeout(boolean isComplex) {
        int timeout = isComplex ? 60 : 10;
        
        TransactionTemplate dynamicTemplate = new TransactionTemplate(
            transactionTemplate.getTransactionManager()
        );
        dynamicTemplate.setTimeout(timeout);
        
        dynamicTemplate.execute(status -> {
            // 业务逻辑
            return null;
        });
    }
    
    /**
     * 事务监控和诊断
     */
    @Component
    @Aspect
    @Slf4j
    public class TransactionMonitorAspect {
        
        private final ThreadLocal<Long> startTime = new ThreadLocal<>();
        
        @Around("@annotation(transactional)")
        public Object monitorTransaction(ProceedingJoinPoint joinPoint, 
                                       Transactional transactional) throws Throwable {
            startTime.set(System.currentTimeMillis());
            
            String methodName = joinPoint.getSignature().toShortString();
            int timeout = transactional.timeout();
            
            log.info("事务开始: {}, 超时时间: {}秒", methodName, timeout);
            
            try {
                Object result = joinPoint.proceed();
                long duration = System.currentTimeMillis() - startTime.get();
                
                if (duration > timeout * 1000L) {
                    log.warn("事务执行时间接近超时: {}ms, 方法: {}", duration, methodName);
                }
                
                return result;
                
            } catch (TransactionTimedOutException e) {
                log.error("事务超时: {}, 配置超时: {}秒", methodName, timeout, e);
                throw e;
            } finally {
                startTime.remove();
            }
        }
    }
}

11. 完整的事务测试方案

11.1 事务测试完整示例

java 复制代码
@SpringBootTest
@TestPropertySource(properties = {
    "spring.datasource.url=jdbc:h2:mem:testdb",
    "spring.jpa.hibernate.ddl-auto=create-drop"
})
@Slf4j
class TransactionalTest {
    
    @Autowired
    private OrderService orderService;
    
    @Autowired
    private OrderRepository orderRepository;
    
    @Autowired
    private PlatformTransactionManager transactionManager;
    
    @Test
    void testTransactionRollbackOnException() {
        // 准备测试数据
        OrderCreateRequest request = createTestRequest();
        request.setAmount(BigDecimal.ZERO);  // 触发异常
        
        // 执行测试
        assertThrows(BusinessException.class, () -> {
            orderService.createOrder(request);
        });
        
        // 验证事务回滚
        assertFalse(orderRepository.existsByOrderNo(request.getOrderNo()));
    }
    
    @Test
    void testTransactionCommitSuccess() {
        // 准备测试数据
        OrderCreateRequest request = createTestRequest();
        request.setAmount(BigDecimal.TEN);
        
        // 执行测试
        OrderCreateResult result = orderService.createOrder(request);
        
        // 验证事务提交
        assertTrue(result.isSuccess());
        assertTrue(orderRepository.existsByOrderNo(request.getOrderNo()));
    }
    
    @Test
    void testTransactionPropagation() {
        TransactionStatus status = transactionManager.getTransaction(
            new DefaultTransactionAttribute()
        );
        
        try {
            // 验证事务传播
            boolean isActive = TransactionSynchronizationManager.isActualTransactionActive();
            assertTrue(isActive);
            
            String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
            assertNotNull(transactionName);
            
            transactionManager.commit(status);
        } catch (Exception e) {
            transactionManager.rollback(status);
            throw e;
        }
    }
    
    @Test
    void testTransactionTimeout() {
        TransactionTemplate template = new TransactionTemplate(transactionManager);
        template.setTimeout(1);  // 1秒超时
        
        assertThrows(TransactionTimedOutException.class, () -> {
            template.execute(status -> {
                // 模拟长时间操作
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                return null;
            });
        });
    }
    
    /**
     * 事务测试配置
     */
    @TestConfiguration
    static class TestConfig {
        
        @Bean
        public DataSource dataSource() {
            return new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.H2)
                .setName("testdb")
                .build();
        }
        
        @Bean
        public PlatformTransactionManager transactionManager(DataSource dataSource) {
            return new DataSourceTransactionManager(dataSource);
        }
    }
    
    private OrderCreateRequest createTestRequest() {
        OrderCreateRequest request = new OrderCreateRequest();
        request.setOrderNo("TEST_" + System.currentTimeMillis());
        request.setAmount(BigDecimal.valueOf(100));
        request.setUserId(1L);
        // 设置其他必要字段
        return request;
    }
}

12. 事务监控与运维

12.1 完整的事务监控方案

java 复制代码
@Component
@Slf4j
public class TransactionMonitor {
    
    @Autowired
    private MeterRegistry meterRegistry;
    
    private final Counter transactionCounter;
    private final Timer transactionTimer;
    private final Counter rollbackCounter;
    
    public TransactionMonitor(MeterRegistry meterRegistry) {
        this.transactionCounter = meterRegistry.counter("transaction.total");
        this.transactionTimer = meterRegistry.timer("transaction.duration");
        this.rollbackCounter = meterRegistry.counter("transaction.rollback");
    }
    
    @EventListener
    public void monitorTransactionEvent(ApplicationEvent event) {
        if (event instanceof TransactionCompletedEvent) {
            transactionCounter.increment();
        }
    }
    
    /**
     * 事务健康检查
     */
    @Component
    public class TransactionHealthIndicator implements HealthIndicator {
        
        @Autowired
        private DataSource dataSource;
        
        @Override
        public Health health() {
            try {
                if (dataSource instanceof HikariDataSource) {
                    HikariDataSource hikari = (HikariDataSource) dataSource;
                    HikariPoolMXBean pool = hikari.getHikariPoolMXBean();
                    
                    int activeConnections = pool.getActiveConnections();
                    int maxPoolSize = hikari.getMaximumPoolSize();
                    
                    Health.Builder status = activeConnections > maxPoolSize * 0.8 ? 
                        Health.down() : Health.up();
                    
                    return status
                        .withDetail("activeConnections", activeConnections)
                        .withDetail("idleConnections", pool.getIdleConnections())
                        .withDetail("totalConnections", pool.getTotalConnections())
                        .withDetail("threadsAwaiting", pool.getThreadsAwaitingConnection())
                        .build();
                }
                
                return Health.unknown().build();
            } catch (Exception e) {
                return Health.down(e).build();
            }
        }
    }
    
    /**
     * 长事务检测和告警
     */
    @Component
    public class LongTransactionDetector {
        
        private final Map<String, Long> transactionStartTimes = new ConcurrentHashMap<>();
        
        @Autowired
        private PlatformTransactionManager transactionManager;
        
        @Scheduled(fixedRate = 30000)  // 每30秒检查一次
        public void detectLongTransactions() {
            long currentTime = System.currentTimeMillis();
            long threshold = 30000;  // 30秒阈值
            
            transactionStartTimes.entrySet().removeIf(entry -> {
                long duration = currentTime - entry.getValue();
                
                if (duration > threshold) {
                    log.warn("发现长事务: {}, 持续时间: {}ms", entry.getKey(), duration);
                    // 发送告警
                    alertLongTransaction(entry.getKey(), duration);
                    return true;
                }
                
                return false;
            });
        }
        
        public void recordTransactionStart(String transactionId) {
            transactionStartTimes.put(transactionId, System.currentTimeMillis());
        }
        
        public void recordTransactionEnd(String transactionId) {
            transactionStartTimes.remove(transactionId);
        }
        
        private void alertLongTransaction(String transactionId, long duration) {
            // 发送邮件、短信、钉钉等告警
            log.error("长事务告警: {}, 持续时间: {}ms", transactionId, duration);
        }
    }
}

13. 总结:事务管理最佳实践

13.1 事务设计原则

java 复制代码
/**
 * 事务管理最佳实践总结
 */
public class TransactionBestPractices {
    
    /*
     * 1. 事务边界原则
     *    - 事务应该围绕业务用例,而不是技术细节
     *    - 保持事务简短,避免长事务
     *    - 在服务层管理事务,而不是在DAO层
     */
    
    /*
     * 2. 异常处理原则  
     *    - 明确指定回滚异常类型
     *    - 避免在事务中捕获所有异常
     *    - 将检查异常转换为非检查异常
     */
    
    /*
     * 3. 性能优化原则
     *    - 查询操作使用只读事务
     *    - 合理设置事务超时时间
     *    - 避免在事务中进行远程调用和IO操作
     */
    
    /*
     * 4. 复杂度控制原则
     *    - 避免嵌套事务的过度使用
     *    - 明确事务传播行为
     *    - 使用编程式事务处理复杂场景
     */
    
    /*
     * 5. 监控运维原则
     *    - 监控事务执行时间和成功率
     *    - 设置长事务告警
     *    - 定期审查事务配置
     */
}

13.2 事务配置检查清单

java 复制代码
@Component
public class TransactionChecklist {
    
    /**
     * 事务配置验证
     */
    public void validateTransactionConfiguration() {
        checkPublicMethods();
        checkExceptionConfiguration();
        checkTimeoutSettings();
        checkDataSourceConfiguration();
        checkAopConfiguration();
    }
    
    private void checkPublicMethods() {
        // 验证所有@Transactional方法都是public
    }
    
    private void checkExceptionConfiguration() {
        // 验证回滚异常配置
    }
    
    private void checkTimeoutSettings() {
        // 验证超时时间配置
    }
    
    private void checkDataSourceConfiguration() {
        // 验证数据源和连接池配置
    }
    
    private void checkAopConfiguration() {
        // 验证AOP代理配置
    }
}

通过以上完整的示例和解决方案,相信你已经对Spring事务失效的各种场景有了全面的理解。记住,事务管理不仅是技术问题,更是架构设计和业务理解的体现。

在你的项目中还遇到过哪些有趣的事务问题?欢迎在评论区分享交流! 🚀

相关推荐
子夜master2 小时前
玩转EasyExcel,看这一篇就够了!!(合并导入 自定义导出 动态表头 合并单元格)
后端
武子康2 小时前
大数据-131 Flink CEP 实战 24 小时≥5 次交易 & 10 分钟未支付检测 案例附代码
大数据·后端·flink
Postkarte不想说话3 小时前
Cisco配置PIM-DM
后端
程序猿有风3 小时前
Java GC 全系列一小时速通教程
后端·面试
BingoGo3 小时前
PHP 8.5 新特性 闭包可以作为常量表达式了
后端·php
SimonKing3 小时前
Komari:一款专为开发者打造的轻量级服务“看守神器”
后端
间彧3 小时前
Spring Security如何解析JWT,并自行构造SecurityContex
后端
Tech_Lin3 小时前
前端工作实战:如何在vite中配置代理解决跨域问题
前端·后端
间彧3 小时前
在Spring Cloud Gateway中如何具体实现JWT验证和用户信息提取?
后端