@Transactional 无效的解决方案

本文探讨了Spring声明式事务管理中的一个问题:当一个方法调用同一类内另一个声明了事务的方法时,事务配置可能失效。文章分析了问题原因,并提供了解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关于@Transactional注解 一般都认为要注意以下三点

1 .在需要事务管理的地方加@Transactional 注解。@Transactional 注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上 。

2 . @Transactional 注解只能应用到 public 可见度的方法上 。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错, 但是这个被注解的方法将不会展示已配置的事务设置。

3 . 注意仅仅 @Transactional 注解的出现不足于开启事务行为,它仅仅 是一种元数据。必须在配置文件中使用配置元素,才真正开启了事务行为。

最近在项目中发现注解无效,经过跟踪源代码发现了问题,于是在网上找到相同出现此问题的人,以下为原文,讲解的很详细:

只要避开Spring目前的AOP实现上的限制,要么都声明要事务,要么分开成两个类,要么直接在方法里使用编程式事务
[问题]
Spring的声明式事务,我想就不用多介绍了吧,一句话“自从用了Spring AOP啊,事务管理真轻松啊,真轻松;事务管理代码没有了,脑不酸了,手不痛了,一口气全配上了事务;轻量级,测试起来也简单,嘿!”。不管从哪个角度看,轻量级声明式事务都是一件解放生产力的大好事。所以,我们“一直用它”。

  不过,最近的一个项目里,却碰到了一个事务管理上的问题:有一个服务类,其一个声明了事务的方法,里面做了三次插入SQL操作,但是在后面出错回滚时,却发现前面插入成功了,也是说,这个声明了事务的方法,实际上并没有真正启动事务!怎么回事呢?难道Spring的声明式事务失效了?

[探幽]
其实以前也会碰到有人说,Spring的事务配置不起作用,但是根据第一反应和以往经验,我总会告诉他,肯定是你的配置有问题啦;所以这一次,我想也不会例外,大概是把事务注解配在了接口上而不是实现方法上,或者,如果是用XML声明方式的话,很可能是切入点的表达式没有配对。

 不过,在检查了他们的配置后,却发现没有配置问题,该起事务的实现方法上,用了@Transactional事务注解声明,XML里也配了注解驱动<tx:annotation-driven .../>,配置很正确啊,怎么会不起作用?

 我很纳闷,于是往下问:
 问1:其他方法有这种情况么?
 答1:没有。
 问2:这个方法有什么特别的么(以下简称方法B)?
 答2:就是调后台插了三条记录啊,没啥特别的。
 问3:这个方法是从Web层直接调用的吧?
 答3:不是,是这个Service类(以下简称ServiceA)的另外一个方法调过来的(以下简称方法A)。
 问4:哦,那个调用它的方法配了事务么(问题可能在这了)?
 答4:没有。
 问5:那WEB层的Action(用的是Struts2),调用的是没有声明事务的方法A,方法A再调用声明了事务的方法B?
 答5:对的。
 问6:你直接在方法A上加上事务声明看看
 答6:好。。。

 看来可能找到问题所在了,于是把@Transactional也加在方法A上,启动项目测试,结果是:事务正常生效,方法A和方法B都在一个事务里了。

 好了,现在总结一下现象:
 1、ServiceA类为Web层的Action服务
 2、Action调用了ServiceA的方法A,而方法A没有声明事务(原因是方法A本身比较耗时而又不需要事务)
 3、ServiceA的方法A调用了自己所在class的方法B,而方法B声明了事务,但是方法B的事务声明在这种情况失效了。
 4、如果在方法A上也声明事务,则在Action调用方法A时,事务生效,而方法B则自动参与了这个事务。 

   我让他先把A也加上事务声明,决定回来自己再测一下。

   这个问题,表面上是事务声明失效的问题,实质上很可能是Spring的AOP机制实现角度的问题。

我想到很久以前研究Spring的AOP实现时发现的一个现象:对于以Cglib方式增强的AOP目标类,会创建两个对象,一个事Bean实例本身,一个是Cglib增强代理对象,而不仅仅是只有后者。我曾经疑惑过这一点,但当时没有再仔细探究下去。

  我们知道,Spring的AOP实现方式有两种:1、Java代理方式;2、Cglib动态增强方式,这两种方式在Spring中是可以无缝自由切换的。

Java代理方式的优点是不依赖第三方jar包,缺点是不能代理类,只能代理接口。
Spring通过AopProxy接口,抽象了这两种实现,实现了一致的AOP方式:
这里写图片描述
现在看来,这种抽象同样带了一个缺陷,那就是抹杀了Cglib能够直接创建普通类的增强子类的能力,Spring相当于把Cglib动态生成的子类,当普通的代理类了,这也是为什么会创建两个对象的原因。下图显示了Spring的AOP代理类的实际调用过程:
这里写图片描述

因此,从上面的分析可以看出,methodB没有被AopProxy通知到,
导致最终结果是:
被Spring的AOP增强的类,在同一个类的内部方法调用时,其被调用方法上的增强通知将不起作用。

  而这种结果,会造成什么影响呢:
  1:内部调用时,被调用方法的事务声明将不起作用
  2:换句话说,你在某个方法上声明它需要事务的时候,如果这个类还有其他开发者,你将不能保证这个方法真的会在事务环境中
  3:再换句话说,Spring的事务传播策略在内部方法调用时将不起作用。

不管你希望某个方法需要单独事务,是RequiresNew,还是要嵌套事务,要Nested,等等,统统不起作用。
4:不仅仅是事务通知,所有你自己利用Spring实现的AOP通知,都会受到同样限制。。。。

[解难]

  问题的原因已经找到,其实,我理想中的AOP实现,应该是下面这样:

这里写图片描述

只要一个Cglib增强对象就好,对于Java代理方式,我的选择是毫不犹豫的抛弃。
至于前面的事务问题,只要避开Spring目前的AOP实现上的限制,要么都声明要事务,要么分开成两个类,要么直接在方法里使用编程式事务,那么一切OK。

<think>我们正在讨论Spring框架中的@Transactional注解的使用方法。根据用户的问题,我们需要解释如何正确使用该注解进行事务管理。结合提供的引用内容,我们可以总结出以下关键点: 1. **启用事务管理**:在Spring Boot中,可以通过在启动类上添加`@EnableTransactionManagement`注解来启用事务管理功能(引用[1])。 2. **注解的位置**:`@Transactional`注解可以用于类或方法上。当用于类上时,表示所有public方法都启用事务;用于方法上时,则只对该方法启用事务(引用[2])。 3. **方法可见性**:使用`@Transactional`注解的方法必须是public的(引用[3])。如果方法不是public(如protected、private或包可见),则事务不会生效。 4. **同类内部调用问题**:在同一个类中,一个没有事务的方法调用另一个有`@Transactional`注解的方法,事务是不会生效的(引用[4])。这是因为事务是通过代理实现的,而内部调用不会经过代理。 5. **多数据源事务**:当涉及多个数据源时,需要指定事务管理器(引用[5])。可以通过在`@Transactional`注解中指定`transactionManager`属性来使用特定的事务管理器。对于不同数据源的操作,可能需要使用编程式事务管理(如`TransactionTemplate`)来分别控制。 接下来,我们将按照用户的要求,结构化地解释如何正确使用`@Transactional`注解。 ### 1. 基本使用 在Spring Boot项目中,首先确保已经配置了事务管理器(通常Spring Boot会自动配置)。然后在需要事务管理的方法上添加`@Transactional`注解。 ```java @Service public class UserService { @Autowired private UserRepository userRepository; @Transactional public void createUser(User user) { userRepository.save(user); } } ``` ### 2. 事务的传播行为 `@Transactional`注解可以设置传播行为(propagation),例如: - `Propagation.REQUIRED`(默认):如果当前存在事务,则加入该事务;如果不存在,则创建一个新的事务。 - `Propagation.REQUIRES_NEW`:总是新建一个事务,如果当前存在事务,则挂起当前事务。 ```java @Transactional(propagation = Propagation.REQUIRES_NEW) public void updateUser(User user) { // 更新用户 } ``` ### 3. 事务的隔离级别 通过`isolation`属性设置隔离级别,例如: ```java @Transactional(isolation = Isolation.READ_COMMITTED) public User getUser(Long id) { return userRepository.findById(id).orElse(null); } ``` ### 4. 回滚规则 默认情况下,事务只在运行时异常(`RuntimeException`)和错误(`Error`)时回滚。可以通过`rollbackFor`和`noRollbackFor`属性自定义回滚规则。 ```java @Transactional(rollbackFor = Exception.class) // 所有异常都回滚 public void saveUser(User user) throws Exception { // 保存用户,如果抛出Exception则回滚 } ``` ### 5. 同类方法调用事务失效问题 避免在同一个类中非事务方法调用事务方法。如果需要,可以将事务方法移到另一个Bean中,或者通过AopContext获取当前代理对象来调用(需要开启`@EnableAspectJAutoProxy(exposeProxy = true)`)。 ```java @Service public class UserService { public void outerMethod() { // 错误:直接调用内部事务方法,事务不生效 innerMethod(); // 正确:通过代理调用 ((UserService) AopContext.currentProxy()).innerMethod(); } @Transactional public void innerMethod() { // 事务操作 } } ``` ### 6. 多数据源事务管理 当使用多个数据源时,每个数据源需要配置自己的事务管理器。在操作不同数据源时,需要指定对应的事务管理器。 ```java @Service public class OrderService { @Transactional(transactionManager = "orderTransactionManager") public void placeOrder(Order order) { // 操作订单数据源 } } @Service public class UserService { @Transactional(transactionManager = "userTransactionManager") public void updateUser(User user) { // 操作用户数据源 } } ``` 如果需要在同一个方法中操作多个数据源(分布式事务),可以使用JTA(如Atomikos)或基于消息的最终一致性方案。Spring Boot也提供了`@Transactional`配合`JtaTransactionManager`的支持,但配置较为复杂。 ### 7. 只读事务 对于只执行查询操作的方法,可以设置`readOnly=true`,以优化性能。 ```java @Transactional(readOnly = true) public List<User> getAllUsers() { return userRepository.findAll(); } ``` ### 注意事项 - 确保数据库引擎支持事务(如InnoDB)。 - 避免在事务方法中进行长时间的操作,以免占用数据库连接。 - 在需要事务的方法中,避免捕获异常而不抛出,否则事务不会回滚。 通过以上步骤,可以正确使用`@Transactional`注解进行事务管理。 §§相关问题§§ 1. 如何解决Spring事务失效的常见问题? 2. 在分布式系统中如何管理事务? 3. @Transactional注解的传播行为有哪些?分别适用于什么场景? 4. 如何配置多数据源的事务管理器? 5. 为什么@Transactional注解的方法必须是public的?</think>### 正确使用 `@Transactional` 注解进行事务管理 `@Transactional` 是 Spring 框架提供的事务管理注解,用于声明式事务控制。以下是关键使用要点和最佳实践: #### 一、基础配置步骤 1. **启用事务管理** 在 Spring Boot 启动类添加注解: ```java @SpringBootApplication @EnableTransactionManagement // 启用事务管理[^1] public class Application { ... } ``` 2. **在方法或类上添加注解** ```java @Service public class UserService { @Transactional // 声明该方法需要事务[^2] public void updateUser(User user) { // 数据库操作 userRepository.save(user); logRepository.insertLog(user.getId()); } } ``` --- #### 二、核心使用规则 1. **方法可见性要求** - **必须为 `public` 方法** 非 public 方法(protected/private/包可见)添加 `@Transactional` 无效[^3]。 ```java // ✅ 有效 @Transactional public void validMethod() {...} // ❌ 无效(不会报错但事务不生效) @Transactional private void invalidMethod() {...} ``` 2. **避免同类内部调用** **问题**:同类中非事务方法调用事务方法时,事务不生效[^4]。 **错误示例**: ```java public void outerMethod() { innerMethod(); // 事务失效! } @Transactional public void innerMethod() {...} ``` **解决方案**: - 将事务方法移到另一个 Bean - 通过代理对象调用: ```java @Autowired private ApplicationContext context; public void outerMethod() { context.getBean(UserService.class).innerMethod(); // 通过代理调用 } ``` --- #### 三、高级配置选项 1. **指定事务管理器(多数据源)** 当存在多个数据源时,需明确指定事务管理器[^5]: ```java @Transactional(transactionManager = "orderTransactionManager") public void placeOrder() { orderRepository.save(...); // 操作订单库 } ``` 2. **自定义事务属性** ```java @Transactional( propagation = Propagation.REQUIRED, // 事务传播行为(默认) isolation = Isolation.DEFAULT, // 隔离级别 timeout = 30, // 超时时间(秒) rollbackFor = Exception.class // 回滚的异常类型 ) public void businessMethod() {...} ``` 3. **多数据源事务嵌套** 使用 `TransactionTemplate` 编程式事务管理: ```java @Transactional(transactionManager = "orderTM") public void crossDatabaseOp() { orderRepo.save(...); new TransactionTemplate(userTM).execute(status -> { userRepo.update(...); // 操作用户库 return null; }); } ``` --- #### 四、常见问题解决 1. **事务不生效的场景** - 方法非 public - 同类内部调用 - 异常被捕获未抛出 - 数据库引擎不支持事务(如 MyISAM) 2. **强制回滚操作** 手动触发回滚: ```java @Transactional public void update() { try { // ... } catch (Exception e) { TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } } ``` --- #### 五、最佳实践 1. **事务粒度控制** - 在 **Service 层** 添加注解,而非 DAO 层 - 保持事务方法精简,避免长时间占用连接 2. **只读事务优化** 对查询方法启用只读事务提升性能: ```java @Transactional(readOnly = true) public User getUserById(Long id) {...} ``` 3. **异常处理** - 默认回滚:`RuntimeException` 和 `Error` - 需自定义回滚时:`@Transactional(rollbackFor = CustomException.class)` > 通过遵循以上规则,可确保数据库操作的原子性和一致性。实际开发中需结合业务场景配置传播行为和隔离级别。
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值