Spring事务传播
事务有四大特性,分别如下:
-
原子性(Atomicity):事务是数据库逻辑工作单元,事务中包含的操作要么都执行成功,要么都执行失败。
-
一致性(Consistency):事务执行的结果必须是使数据库数据从一个一致性状态变到另外一种一致性状态。当事务执行成功后就说数据库处于一致性状态。如果在执行过程中发生错误,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这是数据库就处于不一致状态。
-
隔离性(Isolation):一个事务的执行过程中不能影响到其他事务的执行,即一个事务内部的操作及使用的数据对其他事务是隔离的,并发执行各个事务之间无不干扰。
-
持续性(Durability):即一个事务执一旦提交,它对数据库数据的改变是永久性的。之后的其它操作不应该对其执行结果有任何影响。
事务导致的问题
-
脏读:指当一个事务正字访问数据,并且对数据进行了修改,而这种数据还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据还没有提交那么另外一个事务读取到的这个数据我们称之为脏数据
-
不可重复读:指在一个事务内,多次读同一数据。在这个事务还没有执行结束,另外一个事务也访问该同一数据,那么在第一个事务中的两次读取数据之间,由于第二个事务的修改第一个事务两次读到的数据可能是不一样的,这样就发生了在一个事物内两次连续读到的数据是不一样的,这种情况被称为是不可重复读。(针对行内数据字段不一致)
-
幻象:一个事务先后读取一个范围的记录,但两次读取的纪录数不同,我们称之为幻象读(两次执行同一条 select 语句会出现不同的结果,第二次读会增加一数据行)(针对查询结果集条数不一致)
不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表
Spring中事务传播属性
Spring中事务很很多功能都是Spring借助底层资源的功能来完成的,但是事务的传播行为是通过Spring框架自身实现的,Spring中定义了7个以PROPAGATION_开头的常量表示它的传播属性,具体如下:
-
PROPAGATION_REQUIRED:如果存在一个事务,则支持当前事务。如果没有事务则开启
-
PROPAGATION_SUPPORTS:如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行
-
PROPAGATION_MANDATORY:如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常
-
PROPAGATION_REQUIRES_NEW:总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起
-
PROPAGATION_NOT_SUPPORTED:总是非事务地执行,并挂起任何存在的事务
-
PROPAGATION_NEVER: 总是非事务地执行,如果存在一个活动事务,则抛出异常
-
PROPAGATION_NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, PROPAGATION_REQUIRED 属性执行
Spring中事务的隔离级别
-
ISOLATION_DEFAULT:这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别
-
ISOLATION_READ_UNCOMMITTED :这是事务最低的隔离级别,它充许别外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。
-
ISOLATION_READ_COMMITTED :保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。:
-
ISOLATION_REPEATABLE_READ :这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。
-
ISOLATION_SERIALIZABLE :这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻读。、
事务的隔离级别不同,有可能产生不同的错误现象,引用网上常用的一张图说明隔离级别与各种问题的关系:
Spring中的事务超时
事务超时指的是一个事务所允许执行的最长时间,超过该时间限制后事务还没有完成,将自动回滚事务。Spring框架中在 TransactionDefinition接口中以 int 的值来表示超时时间,单位是秒。默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是none,没有超时限制
Spring中事务的回滚规则
Spring事务管理器回滚一个事务的时机是在当前事务的上下文中抛出异常时。Spring事务管理器会捕捉到未处理的异常,然后根据规则决定是否需要回滚当前事务。
默认配置下,spring只有在抛出RuntimeException 异常(Errors也会导致事务回滚)时才回滚,而抛出checked异常则不会导致事务回滚。但是我们也可以明确的配置在抛出哪些异常时回滚事务,包括checked异常(或者配置为不回滚)。
Spring中事务传播行为
下面通过在Springboot中的几个示例,看一下Spring中不同的事务传播行为。
PROPAGATION_REQUIRED
1.A方法开启事务,B方法中抛出异常
复制代码
1 @Service
2 public class ProductServiceA {
3
4 @Autowired
5 private ProductDao productDao;
6
7 @Autowired
8 private ProductServiceB productServiceB;
9
10 @Transactional(propagation = Propagation.REQUIRED)
11 public void A() {
12 productDao.insert();
13 productServiceB.B();
14 }
15 }
16
17 @Service
18 public class ProductServiceB {
19
20 @Autowired
21 private ProductDao productDao;
22
23 @Transactional(propagation = Propagation.REQUIRED)
24 public void B() {
25 productDao.update();
26 throw new ArithmeticException(“测试异常”);
27 }
28 }
复制代码
测试结果:A和B都没有执行成功
2.A方法开启事务,A方法中抛出异常
复制代码
1 @Service
2 public class ProductServiceA {
3
4 @Autowired
5 private ProductDao productDao;
6
7 @Autowired
8 private ProductServiceB productServiceB;
9
10 @Transactional(propagation = Propagation.REQUIRED)
11 public void A() {
12 productDao.insert();
13 productServiceB.B();
14
15 throw new ArithmeticException(“测试异常”);
16 }
17 }
18
19 @Service
20 public class ProductServiceB {
21
22 @Autowired
23 private ProductDao productDao;
24
25 @Transactional(propagation = Propagation.REQUIRED)
26 public void B() {
27 productDao.update();
28 }
29 }
复制代码
测试结果:A和B都没有执行成功
3.A方法没开启事务,A方法中抛出异常
复制代码
1 @Service
2 public class ProductServiceA {
3
4 @Autowired
5 private ProductDao productDao;
6
7 @Autowired
8 private ProductServiceB productServiceB;
9
10 public void A() {
11 productDao.insert();
12 productServiceB.B();
13 throw new ArithmeticException(“测试异常”);
14 }
15 }
16 @Service
17 public class ProductServiceB {
18
19 @Autowired
20 private ProductDao productDao;
21
22 @Transactional(propagation = Propagation.NEVER)
23 public void B() {
24 productDao.update();
25 }
26 }
复制代码
测试结果:A和B都执行成功
4.A方法没开启事务,B方法中抛出异常
复制代码
1 @Service
2 public class ProductServiceA {
3
4 @Autowired
5 private ProductDao productDao;
6 @Autowired
7 private ProductServiceB productServiceB;
8
9 //@Transactional(propagation = Propagation.REQUIRED)
10 public void A() {
11 productDao.insert();
12 productServiceB.B();
13 }
14 }
15 @Service
16 public class ProductServiceB {
17
18 @Autowired
19 private ProductDao productDao;
20
21 @Transactional(propagation = Propagation.REQUIRED)
22 public void B() {
23 productDao.update();
24 throw new ArithmeticException(“测试异常”);
25 }
26 }
复制代码
测试结果:A执行成功,B执行失败
综上,PROPAGATION_REQUIRED属性:如果当执行到B方法时,会判断上下文中是否存在事务,如果存在,则加入,此时方法A和方法B用的是同一个事务,方法A和方法B共存亡。如果不存在则方法B会为自己分配一个事务,该事务与方法A无关,所以这种情况可能出现A成功,B执行失败回滚了。PROPAGATION_REQUIRED属性是Spring框架事务传播的默认属性。
RROPAGATION_REQUIRES_NEW
1.A方法没开启事务,B方法中抛出异常
复制代码
1 @Service
2 public class ProductServiceA {
3
4 @Autowired
5 private ProductDao productDao;
6 @Autowired
7 private ProductServiceB productServiceB;
8
9 @Transactional(propagation = Propagation.REQUIRED)
10 public void A() {
11 productDao.insert();
12 productServiceB.B();
13 }
14 }
15 @Service
16 public class ProductServiceB {
17
18 @Autowired
19 private ProductDao productDao;
20
21 @Transactional(propagation = Propagation.REQUIRES_NEW)
22 public void B() {
23 productDao.update();
24 throw new ArithmeticException(“测试异常”);
25 }
26 }
复制代码
测试结果:A和B都执行失败
2.A方法没开启事务,A方法自定义四回滚异常,B方法中抛出异常
复制代码
1 @Service
2 public class ProductServiceA {
3
4 @Autowired
5 private ProductDao productDao;
6 @Autowired
7 private ProductServiceB productServiceB;
8
9 //自定义回滚异常
10 @Transactional(propagation = Propagation.REQUIRED,noRollbackFor = ArithmeticException.class)
11 public void A() {
12 productDao.insert();
13 productServiceB.B();
14 }
15 }
16 @Service
17 public class ProductServiceB {
18
19 @Autowired
20 private ProductDao productDao;
21
22 @Transactional(propagation = Propagation.REQUIRES_NEW)
23 public void B() {
24 productDao.update();
25 throw new ArithmeticException(“测试异常”);
26 }
27 }
复制代码
测试结果:A方法执行成功,B方法执行失败
3.A方法开启事务,A方法抛异常
复制代码
1 @Service
2 public class ProductServiceA {
3
4 @Autowired
5 private ProductDao productDao;
6 @Autowired
7 private ProductServiceB productServiceB;
8
9 //自定义回滚异常
10 @Transactional(propagation = Propagation.REQUIRED)
11 public void A() {
12 productDao.insert();
13 productServiceB.B();
14 throw new ArithmeticException(“测试异常”);
15 }
16 }
17 @Service
18 public class ProductServiceB {
19
20 @Autowired
21 private ProductDao productDao;
22
23 @Transactional(propagation = Propagation.REQUIRES_NEW)
24 public void B() {
25 productDao.update();
26
27 }
28 }
复制代码
测试结果:A方法执行失败,B方法执行成功
4.A方法开启事务,A方法抛异常
同PROPAGATION_REQUIRED中示例,B方法单独开启一个事务
5.A方法开启事务,B方法抛异常
同PROPAGATION_REQUIRED中示例,B方法单独开启一个事务
综上,RROPAGATION_REQUIRES_NEW属性总是开启一个新的事务,如果已经存在一个事务,则将这个事务挂起。PROPAGATION_REQUIRED 和RROPAGATION_REQUIRES_NEW的事务传递的区别在于后者总是新起一个事务,所以可能存在两个独立的事务。
PROPAGATION_SUPPORTS
当方法B加上@Transactional(propagation = Propagation.SUPPORTS)注解,执行到方法B时,会检查上下文中是不是已经存在事务,存在则加入,此时相当于PROPAGATION_REQUIRED,不存在则以非事务方法运行,此时刚好和RROPAGATION_REQUIRES_NEW相反。这种方式总结起来就是有事务就加入,没有就算。
PROPAGATION_NOT_SUPPORTED
当方法B加上@Transactional(propagation = Propagation.NOT_SUPPORTED)注解,执行方法B时,检查上下文中有没有事务,如果没有则正常以非事务方式执行,如果有事务,则挂起当前事务,继续以非事务方式执行完,然后在继续执行当前事务。这种方式总结起来就是有事务也不支持。方法B一定以非事务方式运行,不存在回滚方法B的可能。
PROPAGATION_NESTED
1.A方法抛出异常
复制代码
1 @Service
2 public class ProductServiceA {
3
4 @Autowired
5 private ProductDao productDao;
6 @Autowired
7 private ProductServiceB productServiceB;
8
9 //自定义回滚异常
10 @Transactional(propagation = Propagation.REQUIRED)
11 public void A() {
12 productDao.insert();
13 productServiceB.B();
14 throw new ArithmeticException(“测试异常”);
15 }
16 }
17 @Service
18 public class ProductServiceB {
19
20 @Autowired
21 private ProductDao productDao;
22
23 @Transactional(propagation = Propagation.NESTED)
24 public void B() {
25 productDao.update();
26
27 }
28 }
复制代码
测试结果:A和B都执行失败。
2.B方法抛出异常,主事务可选择性处理该异常
复制代码
1 @Service
2 public class ProductServiceA {
3
4 @Autowired
5 private ProductDao productDao;
6 @Autowired
7 private ProductServiceB productServiceB;
8
9 //自定义回滚异常
10 @Transactional(propagation = Propagation.REQUIRED,noRollbackFor = ArithmeticException.class)
11 //@Transactional(propagation = Propagation.REQUIRED)
12 public void A() {
13 productDao.insert();
14 productServiceB.B();
15
16 }
17 }
18 @Service
19 public class ProductServiceB {
20
21 @Autowired
22 private ProductDao productDao;
23
24 @Transactional(propagation = Propagation.NESTED)
25 public void B() {
26 productDao.update();
27 throw new ArithmeticException(“测试异常”);
28 }
29 }
复制代码
当方法B加上@Transactional(propagation = Propagation.NESTED)注解,执行方法B时会先判断上下文中是不是存在事务,如果不存在则,相当于RROPAGATION_REQUIRES_NEW属性,新起一个事务运行,如果存在,则在当前事务中嵌套一个事务,此时也出现两个事务,是嵌套关系。如果主事务提交或者回滚,则嵌套事务也将提交或者回滚。嵌套事务异常,主事务可选择回滚还是继续执行,主要是看如何处理这个异常。
PROPAGATION_NESTED与RROPAGATION_REQUIRES_NEW的区别是PROPAGATION_NESTED创建的是外部事务的子事务, 如果外部事务commit/roolback, 嵌套事务也会被commit/rollback,而RROPAGATION_REQUIRES_NEW创建的是一个新的事务,与已存在的事务独立,即方法A和方法B是分别独立的两个事务,方法B如果已经执行,此时方法A抛异常将不影响方法B。
PROPAGATION_NEVER
这种方式表明必须以非事务的方式运行,如果存在事务,则抛异常。
PROPAGATION_MANDATORY
这个和PROPAGATION_NEVER相反,必须以事务的方式运行,不存在则抛异常。
事务的传播行为失效问题
如果在程序中出现在同一个类的内部出现调用另一个@Transactional注解函数,或者在private或者protected方法上使用@Transactional注解,则将可能出现传播行为不起作用。
原因:由于Spring的事务是通过AOP来实现的,Spring中通过动态代理实现Aop功能,不管是jdk动态代理还是cglib动态代理,都不会处理目标类的private和protected方法。而如果在类的内部调用另外一个添加了@Transactional注解的函数,相当于通过this调用函数,而不是代理对象调用该函数,所以Spring也不会处理这个传播行为。