主要解答
@Transactional注解失效的常见情况包括:
- 非public方法:Spring AOP默认只代理
public方法。 - 内部调用:同一类中方法直接调用,绕过代理。
- 异常类型不匹配:默认只回滚
RuntimeException。 - 传播行为不当:如嵌套事务被挂起。
- 多线程调用:事务与线程绑定,异步调用失效。
- 未启用事务管理:未配置
@EnableTransactionManagement或数据源未绑定事务管理器。
解决方法:
- 确保方法为
public。 - 拆分方法或通过代理调用。
- 指定
rollbackOn。 - 调整
propagation。 - 使用
TransactionTemplate或同步调用。 - 检查事务配置。
详细解答
1. 失效情况及原因
(1) 非public方法
- 原因:Spring使用AOP(默认CGLib或JDK动态代理)实现
@Transactional,仅代理public方法。private、protected或default方法不会被代理,事务失效。 - 底层原理:Spring的事务管理器通过代理对象拦截方法,
public方法才会被代理类覆盖。 - 示例:
@Service public class UserService { @Transactional private void updateUser() { // 事务失效 } }
(2) 内部调用(同一类方法直接调用)
- 原因:Spring事务基于AOP代理,内部方法直接调用(如
this.method())不经过代理对象,事务失效。 - 底层原理:AOP代理只拦截外部调用,内部调用不触发代理逻辑。
- 示例:
@Service public class UserService { public void methodA() { methodB(); // 直接调用,事务失效 } @Transactional public void methodB() { // 事务逻辑 } }
(3) 异常类型不匹配
- 原因:
@Transactional默认只回滚RuntimeException和Error,若抛出Checked Exception(如IOException),事务不会回滚。 - 底层原理:Spring的事务管理器检查异常类型,未匹配
rollbackOn的异常不会触发回滚。 - 示例:
@Service public class UserService { @Transactional public void updateUser() throws IOException { throw new IOException("IO Error"); // 不会回滚 } }
(4) 传播行为不当
- 原因:
@Transactional的传播行为(propagation)设置不当,如NOT_SUPPORTED挂起事务,NESTED未正确嵌套。 - 底层原理:Spring事务管理器根据
propagation决定是否创建新事务或挂起当前事务。 - 示例:
@Service public class UserService { @Transactional(propagation = Propagation.NOT_SUPPORTED) public void updateUser() { // 事务被挂起,失效 } }
(5) 多线程调用
- 原因:Spring事务通过
ThreadLocal绑定到当前线程,多线程调用(如ThreadPoolExecutor)会导致事务失效。 - 底层原理:
ThreadLocal中的Connection对象无法跨线程传递,新线程无事务上下文。 - 示例:
@Service public class UserService { private final ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>()); @Transactional public void updateUser() { executor.submit(() -> { // 新线程无事务上下文,失效 }); } }
(6) 未启用事务管理
- 原因:未配置
@EnableTransactionManagement或数据源未绑定事务管理器。 - 底层原理:Spring需要事务管理器(如
DataSourceTransactionManager)支持@Transactional,否则无法生效。 - 示例:
@SpringBootApplication // 缺少 @EnableTransactionManagement public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
2. 解决方案及代码示例
(1) 非public方法
- 解决:将方法改为
public,或使用AspectJ编译时织入(不依赖代理)。 - 示例:
@Service public class UserService { @Transactional public void updateUser() { // 事务生效 } }
(2) 内部调用
- 解决:
- 拆分方法到不同类,跨类调用。
- 使用
@Autowired注入自身,通过代理调用。 - 使用
AopContext.currentProxy()(需启用@EnableAspectJAutoProxy(exposeProxy = true))。
- 示例(通过代理调用):
@Service @EnableAspectJAutoProxy(exposeProxy = true) public class UserService { public void methodA() { UserService proxy = (UserService) AopContext.currentProxy(); proxy.methodB(); // 通过代理调用,事务生效 } @Transactional public void methodB() { // 事务逻辑 } }
(3) 异常类型不匹配
- 解决:使用
rollbackOn指定回滚异常类型。 - 示例:
@Service public class UserService { @Transactional(rollbackOn = Exception.class) public void updateUser() throws IOException { throw new IOException("IO Error"); // 回滚 } }
(4) 传播行为不当
- 解决:根据业务需求调整
propagation,如REQUIRED(默认,创建或加入事务)。 - 示例:
@Service public class UserService { @Transactional(propagation = Propagation.REQUIRED) public void updateUser() { // 事务生效 } }
(5) 多线程调用
- 解决:
- 避免异步调用,使用同步逻辑。
- 使用
TransactionTemplate手动管理事务。
- 示例(
TransactionTemplate):@Service public class UserService { @Autowired private TransactionTemplate transactionTemplate; public void updateUser() { transactionTemplate.execute(status -> { // 事务逻辑 return null; }); } }
(6) 未启用事务管理
- 解决:添加
@EnableTransactionManagement,确保数据源绑定事务管理器。 - 示例:
@SpringBootApplication @EnableTransactionManagement public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } }
3. 性能与注意事项
- 性能:
- 内部调用通过代理可能增加调用开销。
- 频繁使用
TransactionTemplate需注意代码复杂性。
- 注意事项:
- 避免大事务(长事务),可能导致数据库连接池耗尽。
- 分布式事务场景需结合
@GlobalTransactional(如Seata)。
知识拓展与延伸
1. 进阶知识
- Spring事务传播:
REQUIRED:默认,支持当前事务或创建新事务。NESTED:嵌套事务,支持回滚到保存点。NEVER:不允许事务,若存在事务则抛异常。
- 事务隔离级别:
- 可通过
@Transactional(isolation = Isolation.READ_COMMITTED)设置。 - 需注意数据库支持(如MySQL默认
REPEATABLE_READ)。
- 可通过
- 分布式事务:
- 微服务场景下,
@Transactional仅限单库,需使用分布式事务框架(如Seata、TCC)。
- 微服务场景下,
2. 实际应用场景
- Web开发:
@Transactional常用于Service层,确保数据库操作一致性。 - 批量操作:结合线程池时需手动管理事务(参考前述项目经验)。
- 微服务:分布式系统中,需结合消息队列(如RabbitMQ)实现最终一致性。
3. 常见误区
- 嵌套事务误解:误认为
@Transactional默认支持嵌套,需明确设置NESTED。 - 异常捕获:方法内捕获异常(如
try-catch),事务无法感知,导致不回滚。@Transactional public void updateUser() { try { throw new RuntimeException("Error"); } catch (Exception e) { // 异常被捕获,不回滚 } } - 代理模式冲突:Spring Boot默认CGLib代理,若强制接口代理(
spring.aop.proxy-target-class=false),可能导致失效。


920

被折叠的 条评论
为什么被折叠?



