什么是事务
- 事务是一组逻辑上的操作,要么都执行,要么都不执行
- 事务能否生效需要数据库引擎的支持,比如常用的 MySQL 数据库默认使用支持事务的innodb引擎。但是,如果把数据库引擎变为 myisam,那么程序也就不再支持事务了。
事务的ACID特性
- 原子性:一个事务的操作,要么全部执行,要么全部不执行
- 一致性:执行事务前后,数据保持一致
- 隔离性:一个事务的执行不受其他事务的影响
- 持久性:事务提交后对数据库的影响是永久的
并发事务的问题
- 多个任务对同一个数据进行操作
- 丢失修改:多个事务对同一个数据进行修改,之后的事务修改的结果覆盖了之前的。
- 脏读:一个事务对数据的修改还没有提交,另一个事务就访问使用了这个数据。
- 不可重复读:一个事务多次对一个数据读取,在这个中间另一个事务修改了该数据,导致,前一个前后读取的结果不一样。
- 幻读:前一个事务读了几条记录,后一个事务插入或删除了记录,导致前一个事务再次读的时候数据多了或少了。
SQL 标准定义了四个隔离级别
- READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
- READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
- REPEATABLE-READ(可重读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
- SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
Spring对事务的支持
编程式事务管理
- 通过TransactionTemplate和TransactionManager手动管理事务
声明式事务管理
- 使用 @Transactional注解进行事务管理,基于AOP实现的
Spring事务管理接口
- PlatformTransactionManager: (平台)事务管理器接口,Spring 事务策略的核心。
- PlatformTransactionManager 会根据 TransactionDefinition 的定义比如事务超时时间、隔离级别、传播行为等来进行事务管理 ,而 TransactionStatus 接口则提供了一些方法来获取事务相应的状态比如是否新事务、是否可以回滚等等。
- Spring 并不直接管理事务,而是提供了多种事务管理器 。Spring 事务管理器的接口是: PlatformTransactionManager 。通过这个接口,Spring 为各个平台如 JDBC(DataSourceTransactionManager)、Hibernate(HibernateTransactionManager)、JPA(JpaTransactionManager)等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。
1 | public interface PlatformTransactionManager { |
- TransactionDefinition: 事务(属性)定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)。
- 这个类就定义了一些基本的事务属性
- 事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。
1 | // 返回事务的传播行为,默认值为 REQUIRED。 |
- TransactionStatus: 事务运行状态
- TransactionStatus接口用来记录事务的状态 该接口定义了一组方法,用来获取或判断事务的相应状态信息。
1 | public interface TransactionStatus{ |
TransactionDefinition接口中定义五个隔离级别
ISOLATION_DEFAULT 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.另外四个与JDBC的隔离级别相对应;
ISOLATION_READ_UNCOMMITTED 这是事务最低的隔离级别,它充许别外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。
ISOLATION_READ_COMMITTED 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。
ISOLATION_REPEATABLE_READ 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。
ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读。
事务传播行为:
- 解决业务层方法之间相互调用的事务问题
- 当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
TransactionDefinition.PROPAGATION_REQUIRED
- 使用的最多的一个事务传播行为,我们平时经常使用的@Transactional注解默认使用就是这个事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。也就是说:
- 如果外部方法没有开启事务的话,Propagation.REQUIRED修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
- 如果外部方法开启事务并且被Propagation.REQUIRED的话,所有Propagation.REQUIRED修饰的内部方法和外部方法均属于同一事务 ,只要一个方法回滚,整个事务均回滚。
TransactionDefinition.PROPAGATION_REQUIRES_NEW
- 创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
TransactionDefinition.PROPAGATION_NESTED
- 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
- 也就是说:在外部方法未开启事务的情况下Propagation.NESTED和Propagation.REQUIRED作用相同,修饰的内部方法都会新开启自己的事务,且开启的事务相互独立,互不干扰。
- 如果外部方法开启事务的话,Propagation.NESTED修饰的内部方法属于外部事务的子事务,外部主事务回滚的话,子事务也会回滚,而内部子事务可以单独回滚而不影响外部主事务和其他子事务。
TransactionDefinition.PROPAGATION_MANDATORY
- 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
TransactionDefinition.PROPAGATION_SUPPORTS
- 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED
- 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER
- 以非事务方式运行,如果当前存在事务,则抛出异常。
配置事务的属性
- isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。
- propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。
- read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。
- timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
- rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。
- no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。