Spring boot 事务管理 ----17
基于你的 JobPortal 项目「公司 - 岗位」业务场景,覆盖事务原理、只读优化、回滚规则、传播行为、隔离级别、生产避坑、管理员实战功能,全部采用企业级最佳实践写法。
一、前置说明
Spring Boot 引入
spring-boot-starter-data-jpa 后,自动完成事务配置,无需额外依赖,直接使用 @Transactional 注解即可。- 底层基于 AOP 动态代理实现
- 默认使用
DataSourceTransactionManager - 仅对
public方法生效,同类自调用会失效(高频坑点)
二、基础事务:生产级标准用法(175、176 集)
CompanyService.java 核心业务层
java
运行
package com.example.jobportal.service; import com.example.jobportal.dto.CompanyDTO; import com.example.jobportal.entity.Company; import com.example.jobportal.entity.Job; import com.example.jobportal.exception.BusinessException; import com.example.jobportal.repository.CompanyRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; @Service @RequiredArgsConstructor public class CompanyService { private final CompanyRepository companyRepository; /** * 新增公司 + 批量新增岗位 * 核心特性:事务原子性,要么全部成功,要么全部回滚 * rollbackFor = Exception.class:所有异常都回滚(生产必加) */ @Transactional(rollbackFor = Exception.class) public Company createCompanyWithJobs(CompanyDTO dto) { // 1. 保存公司 Company company = new Company(); company.setName(dto.getName()); company.setAddress(dto.getAddress()); Company savedCompany = companyRepository.save(company); // 2. 批量保存岗位 for (String jobTitle : dto.getJobTitles()) { Job job = new Job(); job.setJobTitle(jobTitle); job.setCompany(savedCompany); savedCompany.getJobs().add(job); } // 3. 模拟异常:测试事务回滚 if (dto.getName().contains("测试异常")) { throw new BusinessException("触发异常,事务回滚,公司和岗位都不会存入数据库"); } return savedCompany; } /** * 只读事务优化(对应177集) * readOnly = true:告诉框架这是纯查询,不做增删改 * 优化点:关闭脏检查、优化连接、提升查询性能 * 注意:只读事务里不能执行写操作,否则报错 */ @Transactional(readOnly = true) public List<Company> getAllCompanies() { return companyRepository.findAll(); } /** * 根据ID查询单个公司 */ @Transactional(readOnly = true) public Company getCompanyById(Long id) { return companyRepository.findById(id) .orElseThrow(() -> new BusinessException("公司不存在")); } /** * 更新公司信息 */ @Transactional(rollbackFor = Exception.class) public Company updateCompany(Long id, CompanyDTO dto) { Company company = getCompanyById(id); company.setName(dto.getName()); company.setAddress(dto.getAddress()); return companyRepository.save(company); } /** * 删除公司 * 级联删除所有关联岗位 */ @Transactional(rollbackFor = Exception.class) public void deleteCompany(Long id) { companyRepository.deleteById(id); } }
配套 DTO
java
运行
package com.example.jobportal.dto; import lombok.Data; import java.util.List; @Data public class CompanyDTO { private String name; private String address; private List<String> jobTitles; }
三、@Modifying 与事务(对应 178 集)
JPQL 批量更新 / 删除必须同时加
@Modifying 和 @Transactional,否则会报错。CompanyRepository.java 新增批量更新
java
运行
package com.example.jobportal.repository; import com.example.jobportal.entity.Company; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; @Repository public interface CompanyRepository extends JpaRepository<Company, Long> { /** * 批量更新公司地址 * 1. @Modifying:声明这是写操作,不是查询 * 2. @Transactional:批量操作必须在事务内执行 */ @Modifying @Transactional(rollbackFor = Exception.class) @Query("UPDATE Company c SET c.address = :address WHERE c.id IN :ids") int batchUpdateAddress(@Param("ids") List<Long> ids, @Param("address") String address); }
最佳实践:批量操作建议在 Service 层加事务,Repository 只负责 SQL,职责更清晰。
四、事务回滚规则(对应 179 集)
核心规则大白话
表格
| 异常类型 | 默认是否回滚 | 说明 |
|---|---|---|
RuntimeException 及其子类 |
✅ 是 | 空指针、数组越界、自定义业务异常都属于这一类 |
Error 及其子类 |
✅ 是 | 系统级错误,如 OOM |
Exception 下的 Checked 异常(如 IOException) |
❌ 否 | 受检异常默认不回滚,是最大的坑 |
生产最佳实践
所有
@Transactional 都加上 rollbackFor = Exception.class,确保任何异常都回滚,避免踩坑。错误 vs 正确写法对比
java
运行
// ❌ 坑点写法:遇到 Checked 异常不会回滚 @Transactional public void testCheckedException() throws Exception { // 业务操作 throw new Exception("受检异常,默认不回滚!"); } // ✅ 正确写法:所有异常都回滚 @Transactional(rollbackFor = Exception.class) public void testCheckedExceptionSafe() throws Exception { // 业务操作 throw new Exception("所有异常都会回滚"); }
五、事务传播行为(对应 180 集)
决定「方法 A 调用方法 B 时,事务该怎么处理」,7 种传播行为,重点掌握前 3 种。
演示代码:两个 Service 演示传播行为
1. 主业务 Service
java
运行
package com.example.jobportal.service; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor public class TransactionDemoService { private final SubTransactionService subService; /** * REQUIRED(默认值):有事务就加入,没有就新建 * 子方法异常,主方法一起回滚 */ @Transactional(rollbackFor = Exception.class) public void requiredDemo() { // 主方法操作 System.out.println("主方法执行"); // 调用子方法,共用同一个事务 subService.requiredSub(); } /** * REQUIRES_NEW:总是新建独立事务 * 子方法异常不影响主事务,主方法异常不影响子事务 * 适合:日志记录、操作记录等必须独立成功的场景 */ @Transactional(rollbackFor = Exception.class) public void requiresNewDemo() { System.out.println("主事务执行"); try { subService.requiresNewSub(); // 子方法独立事务 } catch (Exception e) { System.out.println("子方法异常,主事务不受影响"); } } /** * NESTED:嵌套事务,保存点机制 * 子方法回滚只回滚到保存点,不影响主事务 * 主事务回滚,子事务一起回滚 */ @Transactional(rollbackFor = Exception.class) public void nestedDemo() { System.out.println("主事务执行"); try { subService.nestedSub(); } catch (Exception e) { System.out.println("子方法回滚,主事务继续"); } } }
2. 子事务 Service
java
运行
package com.example.jobportal.service; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @Service public class SubTransactionService { // 默认 REQUIRED @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void requiredSub() { System.out.println("子方法:加入主事务"); } // REQUIRES_NEW:新建独立事务 @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) public void requiresNewSub() { System.out.println("子方法:独立新事务"); throw new RuntimeException("子事务异常"); } // NESTED:嵌套事务 @Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class) public void nestedSub() { System.out.println("子方法:嵌套事务"); throw new RuntimeException("嵌套事务回滚"); } }
常用传播行为总结
表格
| 传播行为 | 通俗解释 | 适用场景 |
|---|---|---|
REQUIRED(默认) |
有就加入,没有就开新的 | 绝大多数普通业务 |
REQUIRES_NEW |
不管有没有,都开全新独立事务 | 操作日志、审计记录、必须独立成功的逻辑 |
NESTED |
嵌套事务,子回滚不影响父 | 复杂业务分支处理 |
SUPPORTS |
有事务就用,没有也没事 | 纯查询方法 |
NOT_SUPPORTED |
挂起事务,非事务执行 | 不重要的查询,不想占用事务资源 |
MANDATORY |
必须有事务,没有就报错 | 强制要求在事务内执行的方法 |
NEVER |
必须没事务,有就报错 | 严禁在事务里执行的方法 |
六、事务隔离级别(对应 181 集)
控制「多个并发事务之间的数据可见性」,解决脏读、不可重复读、幻读问题。
4 种隔离级别对比
表格
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 性能 | MySQL 默认 |
|---|---|---|---|---|---|
READ_UNCOMMITTED |
❌ 会发生 | ❌ 会发生 | ❌ 会发生 | 最高 | ❌ 不用 |
READ_COMMITTED |
✅ 避免 | ❌ 会发生 | ❌ 会发生 | 较高 | Oracle 默认 |
REPEATABLE_READ |
✅ 避免 | ✅ 避免 | ❌ 基本避免 | 一般 | ✅ MySQL 默认 |
SERIALIZABLE |
✅ 避免 | ✅ 避免 | ✅ 避免 | 最低 | 几乎不用 |
代码指定隔离级别
java
运行
/** * 手动指定隔离级别为读已提交 * 一般不用改,用数据库默认即可 */ @Transactional( rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED ) public void testIsolation() { // 业务逻辑 }
最佳实践:99% 的场景用数据库默认隔离级别即可,不要为了炫技乱改,改高了性能差,改低了数据乱。
七、生产常见坑点(对应 182 集)
坑 1:同类自调用,事务失效(最高频)
java
运行
@Service public class DemoService { // 直接调用本类方法,不走代理,事务失效 public void outer() { this.inner(); // ❌ 自调用,@Transactional 不生效 } @Transactional(rollbackFor = Exception.class) public void inner() { // 写操作 } }
✅ 解决方法:注入自己、用
AopContext、拆成两个 Servicejava
运行
@Service public class DemoService { // 注入自己,通过代理对象调用 @Autowired private DemoService self; public void outer() { self.inner(); // ✅ 走代理,事务生效 } }
坑 2:方法不是 public
@Transactional 只对 public 方法生效,private/protected 直接失效。坑 3:异常被 try-catch 吞掉
java
运行
@Transactional(rollbackFor = Exception.class) public void test() { try { // 业务操作,抛异常 } catch (Exception e) { // ❌ 异常被吞了,事务感知不到,不会回滚 e.printStackTrace(); } }
✅ 解决:捕获后手动回滚,或者重新抛出
java
运行
catch (Exception e) { // 手动触发回滚 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); }
坑 4:大事务问题
一个事务里包了大量查询、循环操作、远程调用,事务时间过长,占用连接,并发高了直接拖垮数据库。
✅ 原则:事务里只做必要的数据库写操作,查询、远程调用放到事务外。
八、管理员公司管理接口(183~186 集 实战)
AdminCompanyController.java
java
运行
package com.example.jobportal.controller; import com.example.jobportal.common.Result; import com.example.jobportal.dto.CompanyDTO; import com.example.jobportal.entity.Company; import com.example.jobportal.service.CompanyService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/api/admin/companies") @RequiredArgsConstructor @Tag(name = "管理员-公司管理", description = "后台公司增删改查接口") public class AdminCompanyController { private final CompanyService companyService; @GetMapping @Operation(summary = "查询所有公司") public Result<List<Company>> list() { return Result.success(companyService.getAllCompanies()); } @GetMapping("/{id}") @Operation(summary = "查询公司详情") public Result<Company> detail(@PathVariable Long id) { return Result.success(companyService.getCompanyById(id)); } @PostMapping @Operation(summary = "新增公司(连带岗位)") public Result<Company> create(@RequestBody CompanyDTO dto) { return Result.success(companyService.createCompanyWithJobs(dto)); } @PutMapping("/{id}") @Operation(summary = "更新公司信息") public Result<Company> update(@PathVariable Long id, @RequestBody CompanyDTO dto) { return Result.success(companyService.updateCompany(id, dto)); } @DeleteMapping("/{id}") @Operation(summary = "删除公司") public Result<Void> delete(@PathVariable Long id) { companyService.deleteCompany(id); return Result.success(null); } }
配合之前的 Security 配置,该接口路径/api/admin/**需要ADMIN角色才能访问,实现权限 + 事务的完整后台功能。
九、课程对应表
表格
| 集数 | 核心内容 | 对应代码 |
|---|---|---|
| 175 | @Transactional 底层原理 | 前置说明 + AOP 代理原理 |
| 176 | 生产级后端事务实战 | CompanyService 基础事务方法 |
| 177 | readOnly 只读事务优化 | getAllCompanies 等查询方法 |
| 178 | @Modifying 必须性 | Repository 批量更新方法 |
| 179 | 回滚规则:Checked vs Runtime | 回滚规则对比 + rollbackFor 最佳实践 |
| 180 | 事务传播行为详解 | 传播行为演示 Service |
| 181 | 隔离级别选型 | 隔离级别对比 + 代码示例 |
| 182 | 生产常见坑点避坑 | 四大坑点 + 解决方法 |
| 183~186 | 管理员公司 / 雇主管理功能 | AdminCompanyController 完整后台接口 |
十、终极最佳实践总结
- 事务加在 Service 层,不要加在 Controller、Repository
- 必须加
rollbackFor = Exception.class,避免受检异常不回滚 - 查询方法统一加
readOnly = true,优化性能 - 避免大事务,事务里只做核心写操作,查询、远程调用放外面
- 注意自调用失效,同类调用事务不生效
- 不要乱改传播行为和隔离级别,默认值能覆盖 95% 场景
- 捕获异常后记得手动回滚,不要吞掉异常导致事务不回滚

浙公网安备 33010602011771号