Spring 事务注解@Transactional使用注意事项

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/andy_zhang2007/article/details/83624533

缺省情况下,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中的事务出现了提交失败,导致整个方法逻辑内的数据库操作必须要回滚。现在错误就很明确了 : 整个完成订单数据库操作被回滚,像是没事儿发生一样,但是又有一条消息发出声明订单完成了。

由此可见,如果要想在事务完成时发消息,必须不能在上面例子的方法体内发消息,必须想办法让消息的发送发生在事务提交之后。

参考文档

猜你喜欢

转载自blog.csdn.net/andy_zhang2007/article/details/83624533