前言
Spring的事务隔离级别
Spring的事务传播行为
Spring的事务实现方式
事务ACID特性
- 原子性(Atomicity)
- 一致性(Consistency)
- 隔离性(Isolation)
- 持久性(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()
- propagation_required :如果当前没有事务,则新建,若已存在一个事务,则加入该事务(Spring默认)
- propagation_supports :支持当前事务,若没有事务,则非事务执行
- propagation_mandatory :使用当前事务,若没有事务,则抛出异常
- propagation_required_new :新建事务,如果当前存在事务,则把当前事务挂起
- propagation_not_supported :非事务执行,如果当前存在事务,则把当前事务挂起
- propagation_never :非事务执行,如果当前存在事务,则抛出异常
- propagation_nested :如果当前存在事务,则在嵌套事务内执行。若没有事务,则按照按照默认的propagation_required执行
方法中抛出异常会回滚数据库,被检查的异常可以用rollbackFor指定。
事务实现方式
编程式事务管理对基于 POJO 的应用来说是唯一选择。我们需要在代码中调用beginTransaction()、commit()、rollback()等事务管理相关的方法,这就是编程式事务管理。(淘汰了)
基于 TransactionProxyFactoryBean的声明式事务管理(Spring1.x的方式)
基于 @Transactional 注解的声明式事务管理
基于 Aspectj AOP 配置事务
使用事务的注意点
- 在需要事务管理的地方加@Transactional 注解。@Transactional 注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。
- @Transactional 注解只能应用到 public 的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错, 但是这个被注解的方法将不会展示已配置的事务设置。
- Spring团队建议在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。在接口上使用 @Transactional 注解,只能当你设置了基于接口的代理时它才生效。因为注解是不能继承 的,这就意味着如果正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装。
- 注意仅仅 @Transactional 注解的出现不足于开启事务行为,它仅仅 是一种元数据。必须在配置文件中使用配置元素,才真正开启了事务行为。
- 通过元素的 “proxy-target-class” 属性值来控制是基于接口的还是基于类的代理被创建。如果 “proxy-target-class” 属值被设置为 “true”,那么基于类的代理将起作用(这时需要CGLIB库cglib.jar在CLASSPATH中)。如果 “proxy-target-class” 属值被设置为 “false” 或者这个属性被省略,那么标准的JDK基于接口的代理将起作用。
- @Transactional 的事务开启 ,基于接口的或者是基于类的 代理会被创建。所以在同一个类中一个方法调用另一个有事务的方法,事务是不会起作用的。如一个类中的无事务A()方法调用有事务的B()方法,事务不会触发。