缺省情况下,Spring 事务基于Spring AOP
技术,此时使用事务注解 @Transactional
需要留意以下问题 :
1. 不要在 protected
,private
或者包内可见方法上使用注解 @Transactional
;
不要在 protected
,private
或者包内可见方法上使用注解 @Transactional
,用了也无效。
要点 : 缺省情况下,
@Transactional
注解要应用在可见性为public
的方法上 ;
下面信息摘自Spring官方文档 :
Method visibility and @Transactional
When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.
2. @Transactional
注解的方法在自调用场景中无效 ;
自调用(self-invocation
)中@Transactional
事务注解无效的例子 :
@Service
class FooService{
// 该方法有事务注解
@Transactional
public void methodB(){
//...
}
// 注意:该方法没有事务注解
public void methodA(){
// 这里调用了同一个对象的另一个方法 methodB(), 虽然 methodB 上使用了
// 注解 @Transactional, 但事实上却无效
this.methodB();
}
}
要想解决上面自调用方式带来的@Transactional
无效问题,可以使用下面的方法 :
@Service
class BarService{
@Autowired
FooService fooService;
// 该方法会调用服务 fooService 的方法 methodB(),因为当前对象
// 和 fooService 是两个对象,所以解决了上面提到的自调用问题,
// 也就是说,methodB()方法上的事务注解会生效
public void invokeMethodBOfFooService(){
fooService.methodB();
}
}
下面信息摘自Spring官方文档 :
In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional.
3. 不要从@Transactional
方法内向外传递任何对当前事务做结论的消息
例子 :
@Service
class OrderService {
@Transactional
public void completeOrder(int orderId){
// 1. 数据库操作 : 操作订单表,将订单状态修改为完成
final OrderStatus targetStatus=OrderStatus.COMPLETED;
updateOrderStatus(orderId,targetStatus);
// 2. 数据库操作 : 往订单变更记录表中插入一条记录,记录相应的订单变化
addOrderChangeRecord(orderId,targetStatus);
// 3. 发送消息操作 : 向外部通知订单完成的消息
notifyOnOrderComplete(orderId);
}
该例子中,我们模拟了这样一个场景:完成订单,并在订单完成时向外发出消息(这样该消息的订阅者就可以做出相应的处理)。上面的代码表面上看起来正是在做这样的事情,但实际上却是有问题的,现在我们来分析一下这个问题。我们先将上面的逻辑拆解成如下表所示,很快就能指出问题所在:
执行步骤/时序 | 描述 |
---|---|
1.事务开始 | 由Spring通过AOP机制准备事务 |
2.方法开始 | public void completeOrder(int orderId){// 可以简单理解成方法体开始的{位置 |
3.方法内步骤1执行 | updateOrderStatus(orderId,targetStatus); |
4.方法内步骤2执行 | addOrderChangeRecord(orderId,targetStatus); |
5.方法内步骤3执行 | notifyOnOrderComplete(orderId); |
6.方法结束 | }// 可以简单理解成方法体结束的}位置 |
7.事务结束(提交或者回滚) | 由Spring通过AOP机制提交/回滚事务 |
现在想象一下,上面的逻辑执行到了步骤5之后,订单完成的消息已经发了出去,现在该执行步骤6,7了,此时发出去的消息已经被订阅方开始处理了,但是步骤7中的事务出现了提交失败,导致整个方法逻辑内的数据库操作必须要回滚。现在错误就很明确了 : 整个完成订单数据库操作被回滚,像是没事儿发生一样,但是又有一条消息发出声明订单完成了。
由此可见,如果要想在事务完成时发消息,必须不能在上面例子的方法体内发消息,必须想办法让消息的发送发生在事务提交之后。