背景
想象一下,如果没有Spring框架对事务的支持,我们得自行对事物进行管理:
-
获得JDBC连接、
-
关闭JDBC连接、
-
执行JDBC事务提交、
-
执行JDBC事务回滚操作
有了Spring事务框架,我们再也不需要在与事务相关的方法中处理大量的try...catch...finaly代码。
话说在前,Spring 中事务的使用虽然已经相对简单得多,但是,还是有很多的使用及配置规则。
事务管理,本质是AOP的应用。
规则
规则1:Spring事务只针对RuntimeException
默认情况下 Spring 中的 事务处理只对 RuntimeException 方法进行回滚,所以,如果 此处将 Runtime Exception 替换成普通的 Exception 不会产生回滚 效果 。
规则2:声明注解@Transactional同时在类和方法标识,方法级别会覆盖类级别
方法注解->类注解->接口方法注解->接口类注解
对于事务属性的获取规则相信大家都已经很清楚,如果方法中存在事务属性,则使用方法 上的属性,否则使用方法所在的类上的属性,如果方法所在类的属性上还是没有搜寻到对应的 事务属性,那么再搜寻接口中的方法,再没有的话,最后尝试搜寻接口的类上面的声明 。
规则3:事务失效场景
Spring事务在什么情况下会失效?
1.访问权限问题
java的访问权限主要有四种:private、default、protected、public,它们的权限从左到右,依次变大。
如果事务方法的访问权限不是定义成public,这样会导致事务失效,因为spring要求被代理方法必须是public的。
2.方法用final修饰
如果事务方法用final修饰,将会导致事务失效。因为spring事务底层使用了aop,也就是通过jdk动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能。
3.对象没有被spring管理
使用spring事务的前提是:对象要被spring管理,需要创建bean实例。如果类没有加@Controller、@Service、@Component、@Repository等注解,即该类没有交给spring去管理,那么它的方法也不会生成事务。
4.数据库引擎不支持事务
如果MySQL使用的存储引擎是myisam,这样的话是不支持事务的。因为myisam存储引擎不支持事务。
5.通过this进行方法内部调用
如下代码所示,update方法上面没有加 @Transactional 注解,调用有 @Transactional 注解的 updateOrder 方法,updateOrder 方法上的事务会失效。
因为发生了自身调用,调用该类自己的方法,而没有经过 Spring 的代理类,只有在外部调用事务才会生效。
6.未开启事务
如果是spring项目,则需要在配置文件中手动配置事务相关参数。如果忘了配置,事务肯定是不会生效的。
如果是springboot项目,那么不需要手动配置。因为springboot已经在DataSourceTransactionManagerAutoConfiguration类中帮我们开启了事务。
7.吞了异常
有时候事务不会回滚,有可能是在代码中手动catch了异常。因为开发者自己捕获了异常,又没有手动抛出,把异常吞掉了,这种情况下spring事务不会回滚。
实战案例
启动类
使用@EnableTransactionManagement开启事务管理。
@EnableDiscoveryClient
@SpringBootApplication
//测试自定义启动类加载
@EnableSpringStudy
@EnableCircuitBreaker
//mapper扫描的包路径
@MapperScan("com.bryant.mapper")
@EnableTransactionManagement
@EnableWebMvc
publicclassUserServer{
publicstaticvoidmain(String[]args){
SpringApplication.run(UserServer.class,args);
}
}
声明式事务
Spring的声明式事务是通过AOP实现的(环绕通知)开发中经常使用(代码侵入性最小)–推荐使用!
@Transactional(propagation=Propagation.REQUIRED,readOnly=false)
@PostMapping("/user/add_with_ex")
public void addUserWithEx(Useruser)
{
longid = newRandom().nextLong();
UserDetaildetail=UserDetail.builder()
.id(id)
.age(newRandom().nextInt(100))
.email(newRandom().nextInt(100000000)+"@qq.com")
.name("bryant"+newRandom().nextInt(1111))
.tenantId(newRandom().nextLong())
.build();
userService.insert(detail);
throw new NullPointerException("addUserWithEx...");
}
编程式事务
通过TransactionTemplate手动管理事务在实际应用中很少使用,原因是要修改原来的代码,加入事务管理代码(侵入性 )
@PostMapping("/user/add_with_ex2")
public void addUserWithEx2(User user)
{
long id = new Random().nextLong();
transactionTemplate.execute(
new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
UserDetail detail = UserDetail.builder()
.id(id)
.age(new Random().nextInt(100))
.email(new Random().nextInt(100000000) + "@qq.com")
.name("bryant" + new Random().nextInt(1111))
.tenantId(new Random().nextLong())
.build();
userService.insert(detail);
throw new NullPointerException("addUserWithEx...");
}
}
);
}
测试效果
插入操作,最终以为异常被事务切面捕获,从而执行了回滚逻辑。
拓展:PROPAGATION_REQUIRED和PROPAGATION_NESTED区别
PROPAGATION_REQUIRED 和 PROPAGATION_NESTED 是 Spring 框架中事务传播行为的两种类型。
它们之间的主要区别在于如何处理嵌套事务以及事务回滚的行为。
PROPAGATION_REQUIRED是默认的事务传播行为。
当一个事务方法被另一个事务方法调用时:
PROPAGATION_REQUIRED 表示:
-
如果当前没有事务,就新建一个事务。
-
如果已经存在一个事务,就加入到这个事务中。
这意味着,如果在一个事务方法中调用了另一个带有 PROPAGATION_REQUIRED 的事务方法,那么这两个方法将在同一个事务中执行。如果其中一个方法失败并抛出异常,整个事务将回滚。
PROPAGATION_NESTED 表示:
-
如果当前没有事务,就新建一个事务。
-
如果已经存在一个事务,就在当前事务中创建一个保存点(savepoint),然后开启一个嵌套事务。
嵌套事务的特点是,它可以独立于外部事务进行回滚。如果嵌套事务失败并抛出异常,它将回滚到保存点,而不会影响外部事务。如果外部事务失败并回滚,嵌套事务也会被回滚。