天天看点

在 Spring Boot 项目中正确使用 @Transactional 注解

作者:微风01
在 Spring Boot 项目中正确使用 @Transactional 注解

TransactionManager在Spring Boot的事务管理中起什么作用?

当调用带有 @Transactional 注解的方法时,Spring Boot 使用 来TransactionManager创建新事务或加入现有事务。然后TransactionManager管理事务的生命周期,包括根据事务操作的成功或失败来提交或回滚事务。

除了协调跨多个资源的事务之外,TransactionManager还支持不同的事务隔离级别,这决定了事务之间以及底层数据之间的交互方式。Spring Boot 支持多种隔离级别,包括READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ和SERIALIZABLE,允许开发人员为其应用程序选择适当的隔离级别。

@Service
public class UserService {

  @Autowired
  private UserRepository userRepository;

  @Transactional
  public void updateUser(String username, String email) {
    User user = userRepository.findByUsername(username);
    user.setEmail(email);
    // ... 其他操作
  }
}           

在上面的例子中,该updateUser()方法被标记为@Transactional。当调用该方法时,Spring Boot 使用TransactionManager来创建新事务或加入现有事务。

在 Spring Boot 项目中正确使用 @Transactional 注解

@Transactional 和 @Transactional(propagation = Propagation.REQUIRES_NEW) 有什么区别?

  • @Transactional如果不存在事务,则创建一个事务;如果事务已处于活动状态,则加入现有事务。
  • @Transactional(propagation = Propagation.REQUIRES_NEW)创建一个新事务,暂停当前事务(如果存在)。
@Service 
public  class  MyService { 

    @Transactional 
    public  void  methodA () { 
        // ...这里有一些代码
        methodB(); 
        // ...这里有一些代码
    } 

    @Transactional(propagation = Propagation.REQUIRES_NEW) 
    public  void  methodB () { 
        // ...这里有一些代码
    } 
}           

在此示例中,methodA()标记为@Transactional并调用methodB(),后者标记为@Transactional(propagation = Propagation.REQUIRES_NEW)。调用时methodA(),Spring 创建一个事务并将其传播到methodB(). 但是,由于methodB()具有REQUIRES_NEW传播设置,因此它会创建一个新事务,并暂停 methodA() 中的当前事务。

如果一个@Transactional方法调用另一个@Transactional方法会发生什么?

默认情况下,Spring 使用“基于代理”的方法来管理事务。如果一个@Transactional方法调用@Transactional同一类中的另一个方法,则调用是对原始实例(而不是代理)进行的,所以不会应用事务行为。

@Service 
public  class  MyService { 

    @Autowired 
    private MyService self; 

    @Transactional 
    public  void  methodA () { 
        // ...这里有一些代码
        self.methodB(); 
        // ...这里有一些代码
    } 

    @Transactional 
    public  void  methodB () { 
        // ...这里有一些代码
    } 
}           

在此示例中,methodA()调用methodB(),两者都标记为@Transactional。然而,由于 Spring 默认使用“基于代理”的方法,因此methodA()中调用methodB()时事务行为不用应用到methodB()。 要解决此问题,可以使用基于 AspectJ 配置或将该@Transactional方法移至单独的类。

当在不同的 bean 上调用方法时,Spring 如何处理事务?

当调用不同 bean 上的方法时,Spring 在目标 bean 周围创建一个新的代理,这允许它管理事务行为。@Transactional事务的行为由调用方法上注释的传播设置决定。

@Service 
public  class  MyService { 

    @Autowired 
    private OtherService otherService; 

    @Transactional 
    public  void  methodA () { 
        // ...这里有一些代码
        otherService.methodB(); 
        // ...这里有一些代码
    } 
} 

@Service 
public  class  OtherService { 

    @Transactional 
    public  void  methodB () { 
        // ...这里有一些代码
    } 
}           

在此示例中,methodA()调用methodB()不同的 bean ( OtherService)上的methodB(),当methodB()被调用时,Spring 围绕目标 bean 创建一个新代理,并根据调用方法 ( methodA()) 的传播设置应用事务行为。

当标有 @Transactional 的方法抛出未经检查的异常时会发生什么?

当注释为 @Transactional 的方法抛出未经检查的异常时,Spring 默认会自动回滚事务。此行为可确保在发生错误时事务中所做的数据更改不会保留到数据库中。

@Service
@Transactional
public class UserService {

  @Autowired
  private UserRepository userRepository;

  public void updateUser(String username, String email) {
    User user = userRepository.findByUsername(username);
    if (user == null) {
      throw new RuntimeException("未找到用户");
    }
    user.setEmail(email);
    userRepository.save(user);
    throw new RuntimeException("出了问题");
  }
}           

在上面的示例中,该updateUser()方法标记为@Transactional并更新数据库中用户的电子邮件地址。但是,在将更改保存到数据库后,该方法还会引发未经检查的异常。由于未检查异常,Spring 将回滚事务并丢弃对用户电子邮件地址所做的更改。

如果想更改默认行为并允许事务在发生未经检查的异常时提交,可以将该属性添加noRollbackFor到@Transactional注释中:

@Service 
@Transactional(noRollbackFor = RuntimeException.class) 
public  class  UserService { 

  // ...
 }           

在上面的例子中,如果发生RuntimeException,我们告诉Spring不要回滚事务。在某些情况下,即使发生错误,也希望保留在事务中所做的更改,这在这种情况下非常有用。

@Transactional方法的默认回滚行为是什么?

默认情况下,@Transactional方法将在出现任何未经检查的异常时回滚事务。可以使用@Transactional注释的rollbackFor或noRollbackFor属性来自定义此行为。

@Service 
public  class  MyService { 

    @Transactional(rollbackFor = {Exception.class}) 
    public  void  methodA ()  throws Exception { 
        // ...这里的一些代码
        抛出 new  Exception ( "Something goneError" ); } 
        // ...这里有一些代码
    } 
}           

在此示例中,methodA()标记为@Transactional(rollbackFor = {Exception.class})。这意味着如果抛出任何Exception类型(或其子类)的异常,事务将被回滚。如果抛出不同类型的异常,事务将不会回滚。

可以在私有方法上使用@Transactional吗?

答案是:不,@Transactional仅适用于公共方法。Spring 围绕 bean 的公共方法创建代理来管理事务行为。私有方法对代理不可见,因此不能包装在事务上下文中。

@Service 
public  class  MyService { 

    @Transactional 
    public  void  methodA () { 
        // ...这里有一些代码
        methodB(); 
        // ...这里有一些代码
    } 

    private  void  methodB () { 
        // ...这里有一些代码
    } 
}           

在此示例中,methodA()被标记为@Transactional,但methodB()不是。调用时methodA(),Spring 创建一个事务并将其传播到methodB(). 但是,由于methodB()不是公共方法,Spring 无法围绕它创建代理来应用事务行为。要解决此问题,可以将methodB()设为公共方法,或者将@Transactional注释移到另一个同时调用methodA()和methodB()的方法中。

@Transactional 注解如何处理并发问题?

当多个线程同时访问相同的数据时,可能会出现并发问题,从而导致不一致和数据损坏。Spring Boot的@Transactional注解提供了一种处理并发问题的机制,通过序列化修改相同数据的事务来处理并发问题,防止多个线程同时修改相同的数据。

注意 Spring中@Transactional注解使用的默认隔离级别是Isolation.DEFAULT。这意味着 Spring 将使用底层数据源的默认隔离级别,对于大多数数据库来说通常是“读已提交-read committed”。此默认行为可以通过确保事务不会相互干扰来防止大多数并发问题。

@Service
public class UserService { 

  @Autowired
  private UserRepository userRepository; 

  @Transactional 
  public  void  updateUser (String username, String email) { 
    User  user  = userRepository.findByUsername(username); 
    user.setEmail(email); 
    // ...其他操作
  } 
}           

在上面的示例中,该updateUser()方法标记为@Transactional并更新数据库中用户的电子邮件地址。当多个线程尝试同时修改同一用户的电子邮件地址时,Spring 将通过序列化事务来确保在任何给定时间只有一个线程可以修改数据。这可确保数据保持一致并防止竞争条件和其他并发问题。

在 Spring Boot 项目中正确使用 @Transactional 注解

总结

使用 @Transactional 注解可以方便地管理数据库事务,但需要注意合理设置传播属性、处理异常以及避免一些不当的调用方式,以确保事务的正确性和一致性。

在 Spring Boot 项目中正确使用 @Transactional 注解

继续阅读