在Spring开发中,经常会用到这个注解,但它是怎么起到开启事务的作用的,还一知半解,只知道应该是基于AOP的,今天就来分析一波它的用法和具体实现。
Spring 支持两种事务管理方式:编程式和声明式,编程式是在程序中显式地使用TransactionTemplate来控制事务的开启和提交。
声明式事务是使用@Transactional注解,在类或方法上使用这个注解,就可以起到开启事务的作用,声明式事务是基于AOP的方式,在方法前开启一个事务,在方法执行后进行commit,中间进行一些异常的判断和处理。
相比来说,声明式事务使用起来更加优雅,AOP的方式对代码没有侵入性,比较推荐在日常开发中使用。
下面看一下@Transactional注解的各项参数。
timtout是用来设置事务的超时时间,可以看到默认为-1,不会超时。
isolation属性是用来设置事务的隔离级别,数据库有四种隔离级别:读未提交、读已提交、可重复读、可串行化。MySQL的默认隔离级别是可重复读。
readOnly属性用来设置该属性是否是只读事务,只读事务要从两方面来理解:它的功能是设置了只读事务后在整个事务的过程中,其他事务提交的内容对当前事务是不可见的。
那为什么要设置只读事务呢?它的好处是什么?可以看出它的作用是保持整个事务的数据一致性,如果事务中有多次查询,不会出现数据不一致的情况。所以在一个事务中如果有多次查询,可以启用只读事务,如果只有一次查询就无需只读事务了。
另外,使用了只读事务,数据库会提供一些优化。
但要注意的是,只读事务中只能有读操作,不能含有写操作,否则会报错。
propagation属性用来设置事务的传播行为,对传播行为的理解,可以参考如下场景,一个开启了事务的方法A,调用了另一个开启了事务的方法B,此时会出现什么情况?这就要看传播行为的设置了。
REQUIRES_NEW 和 NESTED非常容易混淆,因为它们都是开启了一个新的事务。我去查询了一下它们之间的区别,大概是这样:
REQUIRES_NEW是开启一个完全的全新事务,和当前事务没有任何关系,可以单独地失败、回滚、提交。并不依赖外部事务。在新事务执行过程中,老事务是挂起的。
NESTED也是开启新事务,但它开启的是基于当前事务的 子事务 ,如果失败的话单独回滚,但如果成功的话,并不会立即commit,而是等待外部事务的执行结果,外部事务commit时,子事务才会commit。
在@Transactional注解中,有一个重要属性是roolbackFor,这是用来判断在什么异常下会进行回滚的,当方法内抛出指定的异常时,进行事务回滚。 rollbackForClassName 也是类似的。
rollbackFor有个问题是默认情况会做什么,以前认为默认会对所有异常进行回滚,但其实默认情况下只对RuntimeException回滚。
这个和上面正好相反,用来设置出现指定的异常时,不进行回滚。
Spring在调用事务增强器的代理类时会首先执行TransactionInterceptor进行增强,在TransactionInterceptor的invoke方法中完成事务逻辑。首先看下TransactionInterceptor的类图结构。
在invoke方法里,调用了父类的模板方法invokeWithinTransaction,下面我们看下TransactionAspectSupport类。
可以看到,这个invokeWithinTransaction方法已经包含了事务执行的整个流程,这里是使用了模板模式,具体的实现交给子类去实现。下面我们分析一下其中的重要方法。
下面们看下用来用来获取TransactionStatus的getTransaction方法,这个方法是在AbstractPlatformTransactionManager抽象类中,
可以看到getTransaction是用来做了一些事务的初始化工作,包括一些判断,新建事务等等。
其中一些对已有事务的处理、嵌入式事务的处理的细节,暂时就略过了~
下面我们回到TransactionInterceptor,看一下下一个流程:回滚
上面有个rollbackOn(ex)方法,是用来判断回滚类型的,我们看下它的实现,它有不同的实现类,看下默认的
确实是RuntimeException和Error。
我们接着看一下回滚的实现。
上面的代码中,有保存点的是指的嵌套事务,因为嵌套事务并不是真正的两个事务,所以会有保存点的信息进行回滚。
事务的提交和回滚的流程类似,同样进行了不同事务类型的判断,在此不进行额外的分析。
通过idea的反编译工具,分析了一波代码,同时参考了《Spring源码解析》,练习了一下分析源码的能力,能大体地看出事务的实现过程,大概看过之后感觉Spring这样写是非常合理的,代码非常清晰,每个功能点都拆分地特别细。博客没有分析事务的加载,因为那块和事务本身的处理并没有太大的关系,而是Spring对注解等属性的加载并生成TransactionInterceptor代理对象。
而且Spring里面使用了非常多的模板方法,使用模板方法对我们的好处是,能从大到小、从整体到局部地了解到整个过程,而且大大降低代码的耦合性。以后在编码时也会注意多使用模板方法。
文章同步发表于 https://blog.csdn.net/acingdreamer/article/details/91873745