Spring事务

前言

Spring的事务隔离级别
Spring的事务传播行为
Spring的事务实现方式

事务ACID特性

  1. 原子性(Atomicity)
  2. 一致性(Consistency)
  3. 隔离性(Isolation)
  4. 持久性(Durability)

Spring定义的’5’种事务隔离级别 Isolation

  • READ_UNCOMMITTED :未提交读,允许别的事务看到未提交的数据。

  • READ_COMMITTED :提交读,保证事务修改的数据提交后别的事务才能读取。

  • REPEATABLE_READ :可重复读

  • SERIALIZABLE :可串行化,事务被处理为顺序执行(代价最高但最可靠)

  • DEFAULT :使用数据库默认的隔离级别

脏读、不可重复读、幻读

脏读(修改且未提交引起)

例如:

张三的工资为5000,事务A中把他的工资改为8000,但事务A尚未提交。与此同时,事务B正在读取张三的工资,读取到张三的工资为8000。随后,事务A发生异常,而回滚了事务。张三的工资又回滚为5000。最后,事务B读取到的张三工资为8000的数据即为脏数据,事务B做了一次脏读。
(大部分数据库缺省的事物隔离级别都不会出现这种状况)

不可重复读(修改引起)

例如:
在事务A中,读取到张三的工资为5000,操作没有完成,事务还没提交。
与此同时,事务B把张三的工资改为8000,并提交了事务。随后,在事务A中,再次读取张三的工资,此时工资变为8000。在一个事务中前后两次读取的结果并不致,导致了不可重复读。
(大部分数据库缺省的事物隔离级别都不会出现这种状况)

幻读(添加删除新记录引起)

例如:
A目前工资为5000的员工有10人,事务A读取所有工资为5000的人数为10人。此时,事务B插入一条工资也为5000的记录。这是,事务A再次读取工资为5000的员工,记录为11人。此时产生了幻读。
(大部分数据库缺省的事物隔离级别都会出现这种状况,此种事物隔离级别将带来表级锁)

Spring的7种事务传播行为 Propagation

传播场景:假设外层事务 Service A 的 Method A() 调用 内层Service B 的 Method B()

  1. propagation_required :如果当前没有事务,则新建,若已存在一个事务,则加入该事务(Spring默认
  2. propagation_supports支持当前事务,若没有事务,则非事务执行
  3. propagation_mandatory使用当前事务,若没有事务,则抛出异常
  4. propagation_required_new新建事务,如果当前存在事务,则把当前事务挂起
  5. propagation_not_supported非事务执行,如果当前存在事务,则把当前事务挂起
  6. propagation_never非事务执行,如果当前存在事务,则抛出异常
  7. propagation_nested :如果当前存在事务,则在嵌套事务内执行。若没有事务,则按照按照默认的propagation_required执行

方法中抛出异常会回滚数据库,被检查的异常可以用rollbackFor指定。

事务实现方式

  1. 编程式事务管理对基于 POJO 的应用来说是唯一选择。我们需要在代码中调用beginTransaction()、commit()、rollback()等事务管理相关的方法,这就是编程式事务管理。(淘汰了)

  2. 基于 TransactionProxyFactoryBean的声明式事务管理(Spring1.x的方式)

  3. 基于 @Transactional 注解的声明式事务管理

  4. 基于 Aspectj AOP 配置事务

使用事务的注意点

  1. 在需要事务管理的地方加@Transactional 注解。@Transactional 注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。
  2. @Transactional 注解只能应用到 public 的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错, 但是这个被注解的方法将不会展示已配置的事务设置。
  3. Spring团队建议在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。在接口上使用 @Transactional 注解,只能当你设置了基于接口的代理时它才生效。因为注解是不能继承 的,这就意味着如果正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装。
  4. 注意仅仅 @Transactional 注解的出现不足于开启事务行为,它仅仅 是一种元数据。必须在配置文件中使用配置元素,才真正开启了事务行为。
  5. 通过元素的 “proxy-target-class” 属性值来控制是基于接口的还是基于类的代理被创建。如果 “proxy-target-class” 属值被设置为 “true”,那么基于类的代理将起作用(这时需要CGLIB库cglib.jar在CLASSPATH中)。如果 “proxy-target-class” 属值被设置为 “false” 或者这个属性被省略,那么标准的JDK基于接口的代理将起作用。
  6. @Transactional 的事务开启 ,基于接口的或者是基于类的 代理会被创建。所以在同一个类中一个方法调用另一个有事务的方法,事务是不会起作用的。如一个类中的无事务A()方法调用有事务的B()方法,事务不会触发。