@Transaction

6 阅读4分钟

@Transactional = 把一段代码包进“要么全成功、要么全失败”的数据库事务里

事务

事务可以理解成一组数据库操作的保险箱

事务最关键的两个特性:

  • 原子性:要么都成功,要么都失败
  • 一致性:数据不会出现“写了一半”的怪状态

@Transactioal

@Transactional
public void doSomething() {
    saveA();
    saveB();
}

加上这个注解后,Spring会在方法执行前后帮你做这件事:

开启事务
  ↓
执行方法
  ↓
没异常 → 提交事务
有异常 → 回滚事务

完全不用手写 begin / commit / rollback

但JPA自带方法,如save(),delete()都是自带@Transactional的

如果在类上加@Transactional,则这个类里所有public方法默认都有事务

rollbackFor属性

用来指定:当抛出哪些“异常类型”时,事务也要回滚,因为Spring默认并不是“所有异常都会回滚”

默认为:

异常类型是否回滚
RuntimeException✅ 回滚
Error✅ 回滚
受检异常(Exception)不回滚

也就是说对于:

@Transactional
public void test() throws Exception {
    repository.save(order);
    throw new Exception("出错了");
}

事务不会回滚

如果没有事务

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

@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,它也会帮你更新

什么情况下会有脏检查

脏检查不是随时都有,需要满足三个条件

  1. 实体是“托管状态”
`RefundOrder order = repository.findById(1L).get();`

此时:

  • order 被 Hibernate 托管

  • 放在 Session / Persistence Context 里

  1. 处在事务中(@Transactional)
@Transactional
public void test() {
    ...
}
  1. 修改了实体属性
order.setMoney(new BigDecimal("100"));

满足这三点时,脏检查一定会发生

脏检查是怎么工作的:

  1. 实体第一次加载时:
RefundOrder order = repository.findById(1L).get();

Hibernate会:

  • 从数据库查数据
  • 保存一份“快照”
  1. 你在Java里改对象
order.setMoney(100);
  1. 事务即将提交

Hibernate 在commit前:

  • 对比快照和当前对象
  • 发现不一样->脏了
  1. 自动生成update的SQL

Session:Hibernate的核心概念

Session是什么

Java对象和数据库之间的“中间人”

它负责:

  1. 持久化对象管理(Persistent Context)

    • 把你 new 出来的对象变成“托管对象”(managed entity)

    托管对象 = 正被Hibernate监控的对象,直到你有没有改它,什么时候该同步到数据库

  2. 缓存

    • Session 内部会缓存你查出来的实体,减少重复 SQL
  3. 事务同步

    • 事务提交时,自动把对象状态同步到数据库(自动 flush)
  4. 延迟加载(Lazy Loading)

    • 代理对象在访问时通过 Session 去数据库加载数据

和数据库的关系

  • Session不是数据库
  • 它是 Hibernate 管理对象和数据库连接的工具
  • Session 内部会拿 Connection 去操作数据库

当你在事务里操作JPA/Hibernate:

方法开始 -> Spring 开启事务 -> Hibernate 打开 Session -> 获取 JDBC Connection
  • Session 里保存实体的状态
  • flush / commit 时 → JDBC Connection 执行 SQL

为什么 Lazy Loading 需要 Session?

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 和事务依赖它,但它本身不是数据库