Spring事务
# Spring支持的事务管理方式
编程式事务
声明式事务;
通过AOP实现,@Transactional
# Spring事务管理接口
PlatformTransactionManager
Spring并不直接管理事务,而是提供了多种事务管理器,通过这个接口,Spring为多个平台:JDBC,JPA,Hibernate等提供了对应的事务管理器,具体实现由各个平台;
底层的事务实现取决于数据库;
# 事务传播行为
解决业务层方法之间互相调用的事务问题;
案例:A类的a方法中 调用了 B类的b方法,如果b发生回滚,如何配置事务传播行为才能让a也跟着回滚?
Spring的传播枚举类:
public enum Propagation {
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
NEVER(TransactionDefinition.PROPAGATION_NEVER),
NESTED(TransactionDefinition.PROPAGATION_NESTED);
private final int value;
Propagation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
REQUIRED
支持当前事务,如果当前不存在则新开启一个事务(默认配置);
@Service public class T1Service { @Resource private TestMapper testMapper; @Resource private T2Service t2Service; @Transactional public void func() { testMapper.updateT1(); t2Service.func(); } } @Service public class T2Service { @Resource private TestMapper testMapper; @Transactional public void func() { testMapper.updateT2(); int i = 1 / 0; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28当方法执行到
int i = 1 / 0
时会抛出异常,此时t1、t2
表中的数据都不会被修改,因为这两个方法使用的是同一个事务,所以只要有一个遇到异常,两个更新就都不会成功;NEW
创建一个新事务,如果当前已存在事务则挂起当前事务;
t2
的数据不会被更新;// t1Service public void func() { testMapper.updateT1(); t2Service.func(); } // t2Service @Transactional(propagation = Propagation.REQUIRES_NEW) public void func() { testMapper.updateT2(); int i = 1 / 0; }
1
2
3
4
5
6
7
8
9
10
11
12与REQUIRED区别?
// t1Service @Transactional public void func() { testMapper.updateT1(); t2Service.func(); int i = 1 / 0; } // t2Service @Transactional(propagation = Propagation.REQUIRES_NEW) public void func() { testMapper.updateT2(); }
1
2
3
4
5
6
7
8
9
10
11
12
13t2
的数据不会回滚,t1
的数据会回滚,因为t2
和t1
不是一个事务;NESTED
如果当前存在事务,则在嵌套事务中执行,否则开启一个新事务;
案例1:与REQUIRED区别;
在
t1Service
中调用t2Service
时,对t2Service
抛出的异常进行了捕获,并且自己也没有再抛出;当
t2Service
配置为REQUIRED
时,t1
、t2
都进行了回滚,因为是同一个事务;// t1Service @Transactional public void func() { testMapper.updateT1(); // catch异常的原因是想说明,在t1Service的func方法中,是不会因为调用t2Service遇到异常而被回滚的,因此异常已经被catch了。回滚主要是因为使用的是同一个事务。 try { t2Service.func(); } catch (Exception e) { e.printStackTrace(); } } // t2Service @Transactional(propagation = Propagation.REQUIRED) public void func() { testMapper.updateT2(); int i = 1 / 0; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18当
t2Service
配置为NESTED
,t1
则不会回滚;案例2:与NEW区别;
// t1Service @Transactional public void func() { testMapper.updateT1(); System.out.println(TransactionSynchronizationManager.getCurrentTransactionName()); try { t2Service.func(); } catch (Exception e) { e.printStackTrace(); } } // t2Service @Transactional(propagation = Propagation.REQUIRES_NEW) public void func() { testMapper.updateT2(); System.out.println(TransactionSynchronizationManager.getCurrentTransactionName()); int i = 1 / 0; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18如果是NEW,输出结果为:
com.demo.transaction.service.T1Service.func com.demo.transaction.service.T2Service.func
1
2如果是NESTED,输出结果为:
com.demo.transaction.service.T1Service.func com.demo.transaction.service.T1Service.func
1
2使用
NESTED
时,虽然还是同一个事务,但却可以在多个方法中进行控制;
# 事务失效场景
注解作用private方法上;
private修饰的方法,spring无法为其生成代理;
public void func() { t1Service.func2(); } @Transactional private void func2() { testMapper.updateT1(); int i = 1 / 0; }
1
2
3
4
5
6
7
8final类型方法;
同spring无法生成代理;
方法内部调用;
func2
方法是由func
调用,虽然func2
方法上加了@Transactional
注解,但事务不会生效,testMapper.updateT2()
执行的方法并不会回滚;public void func() { testMapper.updateT1(); func2(); } @Transactional public void func2() { testMapper.updateT2(); int i = 1 / 0; }
1
2
3
4
5
6
7
8
9
10解决方式:
public void func() { testMapper.updateT1(); T1Service t1Service = (T1Service) AopContext.currentProxy(); t1Service.func2(); } @Transactional public void func2() { testMapper.updateT2(); int i = 1 / 0; }
1
2
3
4
5
6
7
8
9
10另一个线程中使用事务;
Spring
事务管理的方式就是通过ThreadLocal
把数据库连接与当前线程绑定,如果新开启一个线程自然就不是一个数据库连接了,自然也就不是一个事务;@Transactional public void func() { testMapper.updateT1(); new Thread(() -> t2Service.func()).start(); int i = 1 / 0; }
1
2
3
4
5
6