在 Spring Boot 中使用事务

当调用使用 @Transactional 注解的方法时,Spring Boot利用事务管理器来创建或加入事务。事务管理器监视事务的生命周期,根据操作结果进行提交或回滚。

事务隔离级别

Spring Boot支持各种事务隔离级别,包括READ_UNCOMMITTED(读取未提交的数据)、READ_COMMITTED(读取已提交的数据)、REPEATABLE_READ(可重复读)、SERIALIZABLE(串行化)。这些级别确定事务如何与其他事务和底层数据交互。根据应用程序的需求选择正确的隔离级别。

java 复制代码
@Service
public class UserService {
  @Autowired
  private UserRepository userRepository;
jj@Transactional
  public void updateUser(String username, String email) {
    User user = userRepository.findByUsername(username);
    user.setEmail(email);
    // ... other operations
  }
}

在上面的示例中,updateUser() 被标记为 @Transactional,允许Spring Boot管理事务的行为。

理解事务传播

事务行为可以根据方法的注解方式而有所不同。以下是一个关键区别:

@Transactional vs. @Transactional(propagation = Propagation.REQUIRES_NEW)

  • @Transactional 创建或加入一个事务。
  • @Transactional(propagation = Propagation.REQUIRES_NEW)
    创建一个新的事务,如果当前存在事务,则将其挂起。
java 复制代码
@Service
public class MyService {
@Transactional
    public void methodA() {
        // ... some code here
        methodB();
        // ... some code here
    }
@Transactional(propagation = Propagation.REQUIRES_NEW)
    public void methodB() {
        // ... some code here
    }
}

在这个例子中,methodA() 调用了 methodB()。由于 REQUIRES_NEW 传播设置,当 methodB() 开始一个新的事务时,methodA() 的事务被挂起。

在同一类内处理事务

当一个 @Transactional 方法调用同一类内的另一个 @Transactional 方法时,Spring 的默认行为值得注意:

默认行为

默认情况下,Spring 使用一种"基于代理"的方法。如果一个 @Transactional 方法调用同一类内的另一个 @Transactional 方法,事务行为不会被应用。

java 复制代码
@Service
public class MyService {
    @Autowired
    private MyService self;
ja   @Transactional
    public void methodA() {
        // ... some code here
        self.methodB();
        // ... some code here
    }
@Transactional
    public void methodB() {
        // ... some code here
    }
}

在这个例子中,methodA() 和 methodB() 都被标记为 @Transactional。然而,由于"基于代理"的方法,当从 methodA() 调用 methodB() 时,事务行为不会被应用。为了解决这个问题,可以考虑使用基于AspectJ的织入(weaving)或将 @Transactional 方法移到一个单独的类中。

跨不同Bean管理事务

当调用另一个Bean上的方法时,Spring会在目标Bean周围创建一个新的代理,使其能够管理事务行为:

java 复制代码
@Service
public class MyService {
@Autowired
    private OtherService otherService;
@Transactional
    public void methodA() {
        // ... some code here
        otherService.methodB();
        // ... some code here
    }
}
@Service
public class OtherService {
   @Transactional
    public void methodB() {
        // ... some code here
    }
}

在这个例子中,methodA() 在一个不同的Bean(OtherService)上调用了 methodB()。Spring会在 OtherService 周围创建一个新的代理,以根据 methodA() 的传播设置应用事务行为。

处理未检查的异常

当一个 @Transactional 方法抛出未检查的异常时,Spring 默认情况下会自动回滚事务。这确保如果发生错误,事务内的数据更改不会被持久化。

java 复制代码
@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 not found");
    }
    user.setEmail(email);
    userRepository.save(user);
    throw new RuntimeException("Something went wrong");
  }
}

在这个例子中,updateUser() 被标记为 @Transactional 并抛出一个未检查的异常。默认情况下,事务将回滚,丢弃对用户电子邮件地址所做的更改。

自定义回滚行为

您可以使用 @Transactional 注解的 rollbackFor 或 noRollbackFor 属性来自定义回滚行为。

java 复制代码
@Service
@Transactional(noRollbackFor = RuntimeException.class)
public class UserService { 
// ...
}

在这个例子中,我们指定 RuntimeException 不应触发回滚。这在您希望在事务内保留更改,即使发生错误时也很有用。

默认的回滚行为

默认情况下,@Transactional 方法在任何未检查的异常发生时都会回滚事务。使用 rollbackFor 或 noRollbackFor 属性来自定义此行为。

私有方法和 @Transactional

@Transactional 仅在公共方法上工作。Spring在公共方法周围创建代理以管理事务行为。私有方法对代理不可见,无法被包装在事务上下文中。

java 复制代码
@Service
public class MyService {
   @Transactional
    public void methodA() {
        // ... some code here
        methodB();
        // ... some code here
    }
    private void methodB() {
        // ... some code here
    }
}

在这个例子中,methodA() 被标记为 @Transactional,但 methodB() 没有。要启用 methodB() 的事务行为,将其设置为公共方法,或将 @Transactional 注解移到同时调用 methodA() 和 methodB() 的方法上。

处理并发问题

Spring Boot 的 @Transactional 注解通过串行化事务提供了处理并发问题的机制。默认的隔离级别通过确保事务不相互干扰来防止大多数并发问题。

java 复制代码
@Service
public class UserService {
  @Autowired
  private UserRepository userRepository;
 @Transactional
  public void updateUser(String username, String email) {
    User user = userRepository.findByUsername(username);
    user.setEmail(email);
    // ... other operations
  }
}

在这个例子中,updateUser() 被标记为 @Transactional,并且Spring确保当多个线程同时尝试修改同一用户的电子邮件地址时,事务是串行化的。这可以防止数据不一致和竞态条件。

请记住,Spring 中 @Transactional 使用的默认隔离级别是 Isolation.DEFAULT,它与底层数据源的默认值一致。这通常导致"读已提交"的隔离级别,适用于大多数数据库。

精通 @Transactional 对于在Spring Boot应用程序中进行有效的事务管理非常重要。

相关推荐
摇滚侠21 小时前
《On Java 中文版 基础卷》阅读笔记 实现隐藏 12
java·数据库·笔记
川石课堂软件测试1 天前
学习软件测试需掌握的一些知识
数据库·python·sql·功能测试·网络协议·mysql·http
2501_937767161 天前
自建 SQL 数据库定时备份踩坑复盘与落地思路
数据库·sql
Cosolar1 天前
不收费、不加水印、数据归你:我做了个开源的在线简历工具
后端·面试·前端框架
silvamux_1 天前
10款模型写种草笔记:可发布性、返工来源与成本数据缺口复盘
java·数据库·笔记
小蒜学长1 天前
基于Spring Boot+Vue3的在线医疗咨询系统的设计与实现(代码+数据库+LW)
java·spring boot·后端·在线问诊·在线医疗咨询系统
陈随易1 天前
下载神器!Motrix 停更三年后重写回归
前端·后端·程序员
每天一道题1 天前
CAP、Raft 与多数派:分布式系统如何达成一致?
分布式·后端·微服务
I Am a robert girl1 天前
从 GameHorizon 拆解多时间尺度游戏评测:数据管线、离线基准与在线回放的三层设计
数据库·游戏·多时间尺度·数据管线·游戏评测·离线基准·在线回放
前端的日常1 天前
一个人+AI做情侣食谱小程序,0 费用的服务器
前端·后端