在上一篇,我们已经大约了解数据库层面的事务,今天让我们再来熟悉下,spring是怎么管理事务的,以及spring用事务的一些坑,我是大白,不对大神们轻点喷。
spring事务
spring事务分为两种:
1.编程式事务
2.声明式事务
主要先熟悉一下声明式事务。(编程式事务,以后也不会在熟悉了!)
spring事务实现方式
第一步:配置xml
<!– 配置事务管理器 –>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!– 启用事务注解 –>
<tx:annotation-driven transaction-manager="transactionManager"/>
第二步:方法上加@Transactional注解
@Transactional
public int save(User user){
userService.insert(user);
...
}
springboot实现方式
直接在需要开启事务的方法上添加注解就可以。由于在springboot项目中解@SpringBootApplication在加载容器的时候,已经开启事务管理的功能了,所以,不需要额外添加@EnableTransactionManagement注解。
@Transactional注解中的属性讲解:
- propagation:指定事务的传播行为(默认值为REQUIRED)。
- isolation:指定事务的隔离级别,最常用的取值READ_COMMITTED。
- 默认异常:RuntimeException或者Error。
- readOnly:指定事务是否为只读,表示这个事务只读取数据但不更新数据。这样可以帮助数据库引擎优化事务.。若真的是一个只读取数据库值的方法,应设置 readOnly=true。
- timeout:指定强制回滚之前事务可以占用的时间。若超过了timeout指定的时间还为执行,则会抛出异常。
事务的传播行为
事务行为 | 说明 |
---|---|
PROPAGATION_REQUIRED | 如果当前有事务在运行,那么就加入当前事务,否则,就开启一个新的事务 |
PROPAGATION_SUPPORTS | 当前如果存在事务,那么就加入当前事务,如果当前没有事务,那么就以非事务的状态运行 |
PROPAGATION_MANDATORY | 当前存在事务就加入当前事务,当前没有事务,就抛出异常 |
PROPAGATION_REQUIRES_NEW | 创建一个新的事务,假如当前有事务,那么就挂起该当前事务 |
PROPAGATION_NOT_SUPPORTED | 以非事务状态运行,如果当前有事务,那么就挂起该当前事务 |
PROPAGATION_NEVER | 不使用事务,如果当前有事务,抛出异常 |
PROPAGATION_NESTED | 如果当前存在事务,那么在嵌套事务中运行,如果当前没有事务,就开启一个新的事务 |
看了说明,还有点懵的同学们,栗子来了:
REQUIRED :
@Transactional(propagation = Propagation.REQUIRED)
public void methodAB(A a,B b){
//数据库中新增A对象
aService.saveA(a);
//数据库中新增B对象
bService.saveB(b);
throws Exception;//在新增B数据时,出现错误抛出异常
}
public void methodC(A a,B b,C c){
//数据库中新增C对象
cService.save(c);
//调用methodAB
methodAB(a,b);
}
上述代码运行的结果是:
C对象新增成功,A、B对象新增失败。因为methodC方法中没有事务,那么C对象新增时没有异常出现,新增成功。执行方法methodAB传播特性为
REQUIRED当前执行没有事务(开启一个新的事务)methodAB发生回滚。A、B新增失败。
还是这个栗子,这时候我们在methodAB也加上注解:
@Transactional(propagation = Propagation.REQUIRED)
public void methodAB(A a,B b){
//数据库中新增A对象
aService.saveA(a);
//数据库中新增B对象
bService.saveB(b);
throws Exception;//在新增B数据时,出现错误抛出异常
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodC(A a,B b,C c){
//数据库中新增C对象
cService.save(c);
//调用methodAB
methodAB(a,b);
}
此时的运行结果:
A,B,C都新增失败,因为methodAB在运行时加入到methodC中的事务中(如果当前有事务在运行,那么就加入当前事务),这时A、B、C共处于一个事务中,这时候抛出异常,事务都产生回滚,此时数据新增失败。
SUPPORTS:
@Transactional(propagation = Propagation.SUPPORTS)
public void methodAB(A a,B b){
//数据库中新增A对象
aService.saveA(a);
//数据库中新增B对象
bService.saveB(b);
throws Exception;//在新增B数据时,出现错误抛出异常
}
public void methodC(A a,B b,C c){
//数据库中新增C对象
cService.save(c);
//调用methodAB
methodAB(a,b);
}
执行结果:
A、C新增成功。B执行失败。因为methodAB运行在methodC中,当前methodC没有声明事务的,所以B在新增时抛出异常,B新增失败,A、C新增成功(如果当前没有事务,那么就以非事务的状态运行)。
MANDATORY:
@Transactional(propagation = Propagation.MANDATORY)
public void methodAB(A a,B b){
//数据库中新增A对象
aService.saveA(a);
//数据库中新增B对象
bService.saveB(b);
}
public void methodC(A a,B b,C c){
//数据库中新增C对象
cService.save(c);
//调用methodAB
methodAB(a,b);
}
执行结果为:
C对象新增成功,A、B对象新增失败。因为当前方法methodC运行的过程中,methodAB的传播属性为MANDATORY(当前没有事务,就抛出异常),所以A、B抛出异常,新增失败。
REQUIRES_NEW:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodAB(A a,B b){
//数据库中新增A对象
aService.saveA(a);
//数据库中新增B对象
bService.saveB(b);
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodC(A a,B b,C c){
//数据库中新增C对象
cService.save(c);
//调用methodAB
methodAB(a,b);
throw Exception;
}
执行结果:
A、B新增成功,C新增失败,因为methodC事务中出现异常,事务发生回滚,C对象存储失败,A、B因为传播属性为REQUIRES_NEW(假如当前有事务,那么就挂起该当前事务)处于一个新的事务中,所以新增成功。
NOT_SUPPORTED:
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void methodAB(A a,B b){
//数据库中新增A对象
aService.saveA(a);
//数据库中新增B对象
bService.saveB(b);
throws Exception;
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodC(A a,B b,C c){
//数据库中新增C对象
cService.save(c);
//调用methodAB
methodAB(a,b);
}
执行结果:
A新增成功,C、B新增失败,因为methodAB方法中出现异常,并且methodAB方法中事务的传播特性是NOT_SUPPORTED(如果当前有事务,那么就挂起该当前事务)所以B新增失败,而methodAB中的异常抛出到methodC中此时C发生回滚,所以A新增成功。
NEVER
@Transactional(propagation = Propagation.NEVER)
public void methodAB(A a,B b){
//数据库中新增A对象
aService.saveA(a);
//数据库中新增B对象
bService.saveB(b);
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodC(A a,B b,C c){
//数据库中新增C对象
cService.save(c);
//调用methodAB
methodAB(a,b);
}
执行结果:
A、B、C都新增失败。因为NEVER传播属性(如果当前有事务,抛出异常)此时A、B抛出异常新增失败,C检测到异常,产生回滚,同样新增失败,所以A、B、C都新增失败。
NESTED
场景1:
@Transactional(propagation = Propagation.NESTED)
public void methodAB(A a,B b){
//数据库中新增A对象
aService.saveA(a);
//数据库中新增B对象
bService.saveB(b);
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodC(A a,B b,C c){
//数据库中新增C对象
cService.save(c);
//调用methodAB
methodAB(a,b);
throws Exception;
}
运行结果:
A、B、C都新增失败。因为在methodC发生异常时,父事务回滚则子事务也跟着回滚了,所以A、B、C都新增失败。
场景2:
@Transactional(propagation = Propagation.NESTED)
public void methodAB(A a,B b){
//数据库中新增A对象
aService.saveA(a);
//数据库中新增B对象
bService.saveB(b);
throws Exception;
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodC(A a,B b,C c,D d){
//数据库中新增C对象
cService.save(c);
try{
//调用methodAB
methodAB(a,b);
}catch{
log.info("===========打印异常===========")
}
dService.save(d);
}
运行结果:
A、B新增失败。C、D新增成功。因为调用方catch了被调方的异常,所以只有子事务回滚了。
NESTED传播特性需要和REQUIRES_NEW、REQUIRED对比着去看。
事务的传播特性就写到这里,接下来记录一下我们在开发中调用事务的时候,那些坑:
1、@Transactional 应用在非 public 修饰的方法上。
2、@Transactional 默认异常为运行时异常(RuntimeException),我们需要指明rollbackFor = Exception.class。
3、同一个类中方法调用,导致@Transactional失效,因为Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。
4、异常被catch捕获导致@Transactional失效
好了,今天就写到这里吧,希望同学们在开发的过程中,能注意到声明式事务的那些坑,避免踩坑。