SpringBoot(五)原理剖析:Transaction原理
事务的基本概念
事务(Transaction)是由一系列对系统中数据进行访问与更新的操作所组成的一个程序 执行逻辑单元(Unit)。一方面,当多个应用程序并发访问数据库时,事务可以在这些应用程序之间提供一个隔离方法,以防止彼此的操作互相干扰。另一方面,事务为数据库操作序列提供了一个从失败中恢复到正常状态的方法, 同时提供了数据库即使在异常状态下仍能保持数据一致性的方法。
事务具有四个特征,分别是原子性(Atomicity )、一致性(Consistency )、隔离性(Isolation) 和持久性(Durability),简称为事务的ACID特性。
- 原子性(Atomicity):原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
- 一致性(Consistency):事务前后数据的完整性必须保持一致。
- 隔离性(Isolation):事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
- 持久性(Durability):持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
分析源码前,先从理论上大概分析下,纯JDBC操作数据库的基本步骤:
- 获取连接 Connection conn = DriverManager.getConnection()
- 开启事务conn.setAutoCommit(true/false);
- 执行CRUD
- 提交事务/回滚事务 conn.commit() / conn.rollback();
- 关闭连接 conn.close();
@Transactional分析
1 @Target({ElementType.TYPE, ElementType.METHOD}) 2 @Retention(RetentionPolicy.RUNTIME) 3 @Inherited 4 @Documented 5 public @interface Transactional { 6 7 /** 8 * Alias for {@link #transactionManager}. 9 */ 10 @AliasFor("transactionManager") 11 String value() default ""; 12 13 /** 14 * A <em>qualifier</em> value for the specified transaction. 15 * 事务管理器 16 */ 17 @AliasFor("value") 18 String transactionManager() default ""; 19 20 /** 21 * The transaction propagation type. 22 * 传播行为定义,枚举类型,是spring独有的事务行为设计,默认为PROPAGATION_REQUIRED(支持当前事务,不存在则新建) 23 */ 24 Propagation propagation() default Propagation.REQUIRED; 25 26 /** 27 * The transaction isolation level. 28 * 隔离级别,对应数据库的隔离级别实现,mysql默认的隔离级别是 read-committed 29 */ 30 Isolation isolation() default Isolation.DEFAULT; 31 32 /** 33 * The timeout for this transaction (in seconds).超时时间,默认使用数据库的超时,mysql默认的事务等待超时为5分钟 34 */ 35 int timeout() default TransactionDefinition.TIMEOUT_DEFAULT; 36 37 /** 38 * A boolean flag that can be set to {@code true} if the transaction is 39 * effectively read-only 是否只读,默认是false 40 */ 41 boolean readOnly() default false; 42 43 /** 44 * Defines zero (0) or more exception {@link Class classes}, which must be subclasses of {@link Throwable}, indicating which exception types must cause 45 * a transaction rollback. 异常回滚列表,默认的是RuntimeException异常回滚 46 */ 47 Class<? extends Throwable>[] rollbackFor() default {}; 48 49 /** 50 * Defines zero (0) or more exception names (for exceptions which must be a subclass of {@link Throwable}), indicating which exception types must cause 51 * a transaction rollback. 导致事务回滚的异常类名字数组 52 */ 53 String[] rollbackForClassName() default {}; 54 55 /** 56 * Defines zero (0) or more exception {@link Class Classes}, which must be subclasses of {@link Throwable}, indicating which exception types must 57 * <b>not</b> cause a transaction rollback.不会导致事务回滚的异常类数组 58 */ 59 Class<? extends Throwable>[] noRollbackFor() default {}; 60 61 /** 62 * Defines zero (0) or more exception names (for exceptions which must be a subclass of {@link Throwable}) indicating which exception types must <b>not</b> 63 * cause a transaction rollback. 不会导致事务回滚的异常类名字数组,必须继承自Throwable 64 */ 65 String[] noRollbackForClassName() default {}; 66 67 }
事务传播行为
事务传播行为(propagation behavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。
Propagation.REQUIRED | 如果有事务, 那么加入事务, 没有的话新建一个(默认情况) |
Propagation.SUPPORTS | 如果其他bean调用这个方法,在其他bean中声明事务,那就用事务。如果其他bean没有声明事务,那就不用事务 |
Propagation.MANDATORY | 必须在一个已有的事务中执行,否则抛出异常 |
Propagation.REQUIRES_NEW | 不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务 |
Propagation.NOT_SUPPORTED | 容器不为这个方法开启事务 |
Propagation.NEVER | 必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反) |
Propagation.NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。 |
PROPAGATION_NESTED 与PROPAGATION_REQUIRES_NEW的异同:
- 二者的相同点是如果不存在一个活动的事务,都会开启一个新的事务。
- 使用 PROPAGATION_REQUIRES_NEW时,内层事务与外层事务就像两个独立的事务一样,一旦内层事务进行了提交后,外层事务不能对其进行回滚。
- 使用PROPAGATION_NESTED时,外层事务的回滚可以引起内层事务的回滚。而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。
事务隔离级别
大多数数据库默认的事务隔离级别是Read committed,比如Sql Server , Oracle。MySQL的默认隔离级别是Repeatable read,原理可以参照:MySQL 事务。
隔离级别 | 说明 | 脏读 | 不可重复读 | 幻读 |
Isolation.DEFAULT | 使用数据库默认的事务隔离级别 | |||
Isolation.READ_UNCOMMITTED |
读未提交,最低的隔离级别, 它充许令外一个事务可以看到这个事务未提交的数据。
|
√ | √ | √ |
Isolation.READ_COMMITTED | 读已提交,保证一个事务修改的数据提交后才能被另外一个事务读取 | × | √ | √ |
Isolation.REPEATABLE_READ | 可重复读 | × | × | √ |
Isolation.SERIALIZABLE | 供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行 | × | × | × |
1. 脏读 :脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。
2. 不可重复读 :是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的情况。
3. 幻读 : 是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。
事务失效场景
1、数据库引擎不支持事务
以 MySQL 为例,其 MyISAM 引擎是不支持事务操作的,InnoDB 才是支持事务的引擎,一般要支持事务都会使用 InnoDB。
2、类没有被 Spring 管理
方法被标注了@Transactional,但是类没有注解,没有被Spring管理
// @Service public class OrderServiceImpl implements OrderService { @Transactional public void updateOrder(Order order) { // update order } }
3、方法调用问题
对象内部方法互相调用不会被Spring的AOP拦截,@transactional注解无效;
事务方法或者类不是public,无法被外部包访问到,或者是final无法继承,@transactional注解无效。
4、事务管理器配置问题
没有指定transactionManager参数,默认的transactionManager并不是期望的,以及一个事务中涉及到了多个数据库。
5、异常被吃了
@Service public class OrderServiceImpl implements OrderService { @Transactional public void updateOrder(Order order) { try { // update order } catch { } } }
6、异常类型错误
@Service public class OrderServiceImpl implements OrderService { @Transactional public void updateOrder(Order order) { try { // update order } catch { throw new Exception("更新错误"); } } }
默认回滚的是:RuntimeException,如果想触发其他异常的回滚,需要在注解上配置一下,如:
@Transactional(rollbackFor = Exception.class)