@Transactional = 把一段代码包进"要么全成功、要么全失败"的数据库事务里
事务
事务可以理解成一组数据库操作的保险箱
事务最关键的两个特性:
- 原子性:要么都成功,要么都失败
- 一致性:数据不会出现"写了一半"的怪状态
@Transactioal
java
@Transactional
public void doSomething() {
saveA();
saveB();
}
加上这个注解后,Spring会在方法执行前后帮你做这件事:
开启事务
↓
执行方法
↓
没异常 → 提交事务
有异常 → 回滚事务
完全不用手写 begin / commit / rollback
但JPA自带方法,如save(),delete()都是自带@Transactional的
如果在类上加@Transactional,则这个类里所有public方法默认都有事务
rollbackFor属性
用来指定:当抛出哪些"异常类型"时,事务也要回滚,因为Spring默认并不是"所有异常都会回滚"
默认为:
| 异常类型 | 是否回滚 |
|---|---|
RuntimeException |
✅ 回滚 |
Error |
✅ 回滚 |
| 受检异常(Exception) | ❌ 不回滚 |
也就是说对于:
java
@Transactional
public void test() throws Exception {
repository.save(order);
throw new Exception("出错了");
}
事务不会回滚
如果没有事务
java
public void refundAndLog(RefundOrder order) {
refundOrderRepository.save(order); // 步骤1:保存退款订单
logRepository.save(new Log("退款成功")); // 步骤2:写日志
}
如果没有@Transactional,save(order)会独立开一个事务提交,save(log)也会再开一个事务提交,如果第二步报错(如数据库断开,或唯一约束冲突)
就会产生结果:
refund_order 表里已经插入了一条数据 log 表没插入
数据库就处于 不一致状态
但如果加上@Transactional,Spring会在方法开始时开启事务,如果第二步报错,则事务回滚,refund_order的插入也被撤销,
推荐用法
- Service层:凡是有"写数据库"的方法,加@Transactional
- 查询可以不加,或者加@Transactional(readOnly = true)
- Controller上不要加,防止事务时间过长,容易锁表
脏检查
脏检查 = Hibernate 在事务提交前,自动检查"被托管的实体对象有没有被改过",如果改过,就自动生成 UPDATE SQL
java
@Service
@Transactional // 注意:类上有事务
public class RefundOrderService {
public RefundOrder getAndFormat(Long id) {
RefundOrder order = repository.findById(id).orElse(null);
// 你只是想格式化一下数据,给前端看
order.setMoney(
order.getMoney().setScale(2, RoundingMode.HALF_UP)
);
return order;
}
}
这段代码看似
也就是说你不用写Update,它也会帮你更新
什么情况下会有脏检查
脏检查不是随时都有,需要满足三个条件
- 实体是"托管状态"
java
`RefundOrder order = repository.findById(1L).get();`
此时:
-
order被 Hibernate 托管 -
放在 Session / Persistence Context 里
- 处在事务中(@Transactional)
java
@Transactional
public void test() {
...
}
- 修改了实体属性
java
order.setMoney(new BigDecimal("100"));
满足这三点时,脏检查一定会发生
脏检查是怎么工作的:
- 实体第一次加载时:
java
RefundOrder order = repository.findById(1L).get();
Hibernate会:
- 从数据库查数据
- 保存一份"快照"
- 你在Java里改对象
java
order.setMoney(100);
- 事务即将提交
Hibernate 在commit前:
- 对比快照和当前对象
- 发现不一样->脏了
- 自动生成update的SQL
Session:Hibernate的核心概念
Session是什么
Java对象和数据库之间的"中间人"
它负责:
-
持久化对象管理(Persistent Context)
- 把你 new 出来的对象变成"托管对象"(managed entity)
托管对象 = 正被Hibernate监控的对象,直到你有没有改它,什么时候该同步到数据库
-
缓存
- Session 内部会缓存你查出来的实体,减少重复 SQL
-
事务同步
- 事务提交时,自动把对象状态同步到数据库(自动 flush)
-
延迟加载(Lazy Loading)
- 代理对象在访问时通过 Session 去数据库加载数据
和数据库的关系
- Session不是数据库
- 它是 Hibernate 管理对象和数据库连接的工具
- Session 内部会拿 Connection 去操作数据库
当你在事务里操作JPA/Hibernate:
rust
方法开始 -> Spring 开启事务 -> Hibernate 打开 Session -> 获取 JDBC Connection
- Session 里保存实体的状态
- flush / commit 时 → JDBC Connection 执行 SQL
为什么 Lazy Loading 需要 Session?
java
RefundOrder order = repository.findById(1L).orElse(null);
System.out.println(order.getItems().size()); // 懒加载触发
items是 LAZY- Hibernate 给你一个代理对象
- 代理对象会去 Session 拿连接,发 SQL
- 如果 Session 关闭(事务结束) → 连接没了 → 报
LazyInitializationException
Session生命周期
| 阶段 | 发生什么 |
|---|---|
| 开启事务 | Session 开始,连接准备好 |
| 执行查询 | Session 缓存实体,生成代理对象 |
| 访问 LAZY 属性 | Session 用 Connection 发 SQL |
| 事务提交/回滚 | Session flush 或清空 |
| 方法返回后 | Session 被关闭,延迟加载不再可用 |
总结
Session 是 Hibernate 管理实体对象、缓存和数据库连接的中间人,Lazy Loading 和事务依赖它,但它本身不是数据库