保亭黎族苗族自治县网站建设_网站建设公司_导航菜单_seo优化
2025/12/23 16:23:47 网站建设 项目流程
Spring 事务失效是日常开发中高频踩坑点,核心原因是事务注解的生效条件未满足(如代理失效、传播行为不当、异常处理错误等)。以下梳理 10 类典型失效场景,结合原理和解决方案,覆盖开发中 90% 以上的失效情况:

核心前提:Spring 事务的生效基础

Spring 事务默认基于 动态代理(JDK 动态代理 / CGLIB) 实现,只有通过代理对象调用方法,事务注解(@Transactional)才会生效;且事务管理器(如DataSourceTransactionManager)需正确配置。所有失效场景本质都是 “脱离了代理的管控” 或 “事务规则未满足”。

一、10 类失效场景 + 原理 + 解决方案

场景 1:方法不是 public(最基础的失效)

原理

Spring 事务的代理逻辑(TransactionInterceptor)仅对 public 方法 生效:
  • JDK 动态代理:基于接口,只能代理 public 方法;
  • CGLIB 代理:虽可代理非 public,但 Spring 源码中硬编码限制仅处理 public(AbstractFallbackTransactionAttributeSourcecomputeTransactionAttribute方法会过滤非 public 方法)。

示例

java
@Service
public class UserService {// 非public方法,事务失效@Transactionalprivate void updateUser(Long id) {// 数据库操作...}
}

解决方案

将事务方法改为 public 修饰(必须)。

场景 2:同类方法内部调用(最易踩坑)

原理

Spring 事务通过代理对象实现,同类内直接调用方法(而非代理对象),会绕过代理逻辑,事务注解失效。

示例

java
@Service
public class UserService {// 无事务的方法public void outerMethod(Long id) {// 内部调用事务方法,绕过代理,事务失效this.innerMethod(id); }@Transactionalpublic void innerMethod(Long id) {// 数据库操作...}
}

解决方案(3 种,按推荐度排序)

  1. 最优:拆分到不同 Service将事务方法抽离到独立的 Service,通过依赖注入调用(走代理):
    java
    @Service
    public class UserService {@Resourceprivate UserTxService userTxService;public void outerMethod(Long id) {userTxService.innerMethod(id); // 走代理,事务生效}
    }@Service
    public class UserTxService {@Transactionalpublic void innerMethod(Long id) {// 数据库操作...}
    }
     
  2. 次优:自注入代理对象在本类中注入自身的代理对象(需开启暴露代理配置):
    java
    // 1. 开启暴露代理(application.yml)
    spring:aop:proxy-target-class: trueexpose-proxy: true// 2. 本类中调用代理对象的方法
    @Service
    public class UserService {public void outerMethod(Long id) {// 获取代理对象并调用((UserService) AopContext.currentProxy()).innerMethod(id);}@Transactionalpublic void innerMethod(Long id) {// 数据库操作...}
    }
     
  3. 兜底:手动编程式事务放弃声明式事务,用TransactionTemplate手动控制:
    java
    @Service
    public class UserService {@Resourceprivate TransactionTemplate transactionTemplate;public void outerMethod(Long id) {transactionTemplate.execute(status -> {innerMethod(id);return null;});}public void innerMethod(Long id) {// 数据库操作...}
    }

场景 3:异常被捕获且未抛出(核心失效场景)

原理

Spring 事务默认只捕获 未处理的 RuntimeException/Error,并在异常抛出时触发回滚;若异常被try-catch捕获且未重新抛出,事务管理器感知不到异常,会执行提交而非回滚。

示例

java
@Service
public class UserService {@Transactionalpublic void updateUser(Long id) {try {// 数据库操作...int i = 1 / 0; // 抛出ArithmeticException} catch (Exception e) {// 捕获异常但未抛出,事务不会回滚log.error("更新失败", e);}}
}

解决方案

  1. 捕获后重新抛出异常(推荐):
    java
    @Transactional
    public void updateUser(Long id) {try {// 数据库操作...int i = 1 / 0;} catch (Exception e) {log.error("更新失败", e);throw new RuntimeException(e); // 重新抛出运行时异常}
    }
     
  2. 手动触发回滚(适合需捕获异常的场景):
    java
    @Transactional
    public void updateUser(Long id) {try {// 数据库操作...int i = 1 / 0;} catch (Exception e) {log.error("更新失败", e);// 手动回滚事务TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}
    }

场景 4:异常类型不匹配(如捕获 Checked 异常)

原理

Spring 事务默认只对 RuntimeException 和 Error 回滚,若抛出的是 Checked 异常(如IOExceptionSQLException),且未配置rollbackFor,事务不会回滚。

示例

java
@Service
public class UserService {@Transactionalpublic void updateUser(Long id) throws IOException {// 数据库操作...throw new IOException("IO异常"); // Checked异常,事务不回滚}
}

解决方案

通过rollbackFor指定需要回滚的异常类型:
java
// 指定回滚所有异常(或精准指定IOException)
@Transactional(rollbackFor = Exception.class)
public void updateUser(Long id) throws IOException {// 数据库操作...throw new IOException("IO异常"); // 事务回滚
}

场景 5:事务传播行为配置错误

原理

@Transactional(propagation = ...) 定义了事务的传播规则,若配置为以下值,会导致事务失效:
  • Propagation.NOT_SUPPORTED:以非事务方式执行,若有当前事务则挂起;
  • Propagation.NEVER:以非事务方式执行,若有当前事务则抛异常;
  • Propagation.SUPPORTS:有事务则加入,无则以非事务执行(无事务时失效)。

示例

java
@Service
public class UserService {// 传播行为为NOT_SUPPORTED,事务失效@Transactional(propagation = Propagation.NOT_SUPPORTED)public void updateUser(Long id) {// 数据库操作...}
}

解决方案

使用正确的传播行为(默认Propagation.REQUIRED即可满足 99% 场景):
java
// 默认REQUIRED:有事务则加入,无则新建
@Transactional
public void updateUser(Long id) {// 数据库操作...
}

场景 6:数据源未配置事务管理器

原理

Spring 事务需要绑定对应的PlatformTransactionManager(如DataSourceTransactionManager),若未配置,事务注解仅为 “空注解”,不会生效。

示例(错误配置)

java
// 仅配置数据源,未配置事务管理器
@Configuration
public class DataSourceConfig {@Beanpublic DataSource dataSource() {HikariDataSource ds = new HikariDataSource();// 配置数据源...return ds;}
}

解决方案

手动配置事务管理器(Spring Boot 2.x+ 若只配置一个数据源,会自动配置,多数据源需手动指定):
java
@Configuration
public class TransactionConfig {@Resourceprivate DataSource dataSource;@Beanpublic PlatformTransactionManager transactionManager() {// 适配JDBC/MyBatis/MyBatis-Plusreturn new DataSourceTransactionManager(dataSource);}
}

场景 7:多数据源未指定事务管理器

原理

多数据源场景下,若未通过@Transactional(value = "xxxTransactionManager")指定对应的事务管理器,Spring 无法识别该用哪个,事务失效。

示例

java
@Service
public class UserService {// 多数据源但未指定事务管理器,失效@Transactionalpublic void updateUser(Long id) {// 数据库操作...}
}

解决方案

指定事务管理器的 bean 名称:
java
// 假设配置了两个事务管理器:txManager1、txManager2
@Transactional(value = "txManager1")
public void updateUser(Long id) {// 数据库操作...
}

场景 8:表不支持事务(如 MyISAM)

原理

Spring 事务依赖数据库的事务支持,若 MySQL 表使用MyISAM引擎(不支持事务),即使配置了 Spring 事务,也无法回滚。

解决方案

将表引擎改为InnoDB(MySQL 5.5+ 默认 InnoDB):
sql
-- 修改表引擎
ALTER TABLE user ENGINE = InnoDB;-- 建表时指定引擎
CREATE TABLE user (id BIGINT PRIMARY KEY
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 

场景 9:代理方式错误(如 JDK 代理 vs CGLIB)

原理

  • JDK 动态代理:基于接口,仅代理接口中的方法;若 Service 未实现接口,且未开启 CGLIB 代理,会导致代理失效;
  • Spring Boot 2.x+ 默认开启proxy-target-class=true(优先 CGLIB),但手动关闭后可能失效。

解决方案

  1. 开启 CGLIB 代理(application.yml):
    yaml
    spring:aop:proxy-target-class: true # 强制CGLIB代理
     
  2. 让 Service 实现接口(适配 JDK 代理):
    java
    public interface UserService {void updateUser(Long id);
    }@Service
    public class UserServiceImpl implements UserService {@Transactional@Overridepublic void updateUser(Long id) {// 数据库操作...}
    }

场景 10:事务超时设置不合理(隐性失效)

原理

@Transactional(timeout = n) 表示事务超时时间(秒),若业务执行时间超过 timeout,事务会被强制回滚,看似 “失效”(实际是超时回滚)。

示例

java
@Transactional(timeout = 1) // 超时1秒
public void updateUser(Long id) throws InterruptedException {Thread.sleep(2000); // 执行时间超过超时时间// 数据库操作... // 事务超时回滚
}
 

解决方案

根据业务实际耗时调整 timeout 值(避免过小):
java
@Transactional(timeout = 30) // 合理的超时时间(30秒)
public void updateUser(Long id) throws InterruptedException {// 业务逻辑...
}
 

二、快速排查事务失效的通用步骤

  1. 检查方法修饰符:是否为 public;
  2. 检查调用方式:是否为同类内部调用(this.xxx ());
  3. 检查异常处理:是否捕获异常未抛出,或异常类型不匹配;
  4. 检查传播行为:是否为 REQUIRED/NESTED/REQUIRES_NEW(避免 NOT_SUPPORTED/NEVER);
  5. 检查事务管理器:是否配置,多数据源是否指定;
  6. 检查数据库引擎:是否为 InnoDB;
  7. 日志排查:开启 Spring 事务日志,查看事务是否创建 / 回滚:
    yaml
    logging:level:org.springframework.transaction: DEBUGorg.springframework.jdbc.datasource.DataSourceTransactionManager: DEBUG

三、总结

Spring 事务失效的核心可归纳为三类:
  1. 代理失效:非 public 方法、同类内部调用、代理方式错误;
  2. 规则不满足:异常处理错误、传播行为错误、超时设置不合理;
  3. 底层不支持:数据源未配事务管理器、表引擎不支持事务。
生产环境中,优先使用声明式事务(@Transactional)+ 避免同类内部调用 + 正确处理异常,复杂场景(如多数据源、手动回滚)可结合编程式事务(TransactionTemplate),既能保证可读性,又能避免失效。
 
 
 

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询