Spring 事务原理

来源:互联网 发布:淘宝鹊桥现状 编辑:程序博客网 时间:2024/06/02 00:13

       事务的管理对于基于数据库的应用程序而言是非常重要的,本文基于Spring Boot来看看Spring是如何管理事务的,本文将从事务的理论、声明式事务配置以及事务管理来展开。

       事务的特性(ACID):原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持续性/永久性(Durability)。在同一个事务当中的SQL要么都成功,要么都失败(回滚),这样就可以保证数据的完整一致,避免出现脏数据。在Spring中添加事务有两种方式:声明式事务和编程式事务。声明式事务即@Transactional或基于TX和AOP的XML配置,编程式事务就是在代码里手工控制事务的开启、关闭、回滚。一般Spring项目都是使用声明式事务,而且使用@Transactional方式的比较多,即方便又灵活。@Transactional中有5个属性,分别是:Propagation(事务传播行为)、Isolation(事务隔离级别)、Rollback Rules(事务回滚规则)、Timeout(事务超时)和Read-Only(事务只读)。

       1、事务传播行为,指的是当要开始一个事务时,当前上下文中已经存在了一个事务,这时事务的执行行为。TransactionDefinition中定义了7种传播行为,使用过程中我们使用Propagation枚举来指定,分别是:REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER、NESTED。Spring默认的传播行为为REQUIRED。下面是各行为解释:

       REQUIRED:如果当前事务已存在,那么就加入该事务,否则自己创建一个新的事务。

       SUPPORTS:如果当前事务已存在,那么就加入该事务,否则以非事务环境执行。

       MANDATORY:如果当前事务已存在,那么就加入该事务,否则抛出异常。

       REQUIRES_NEW:总是会创建一个新的事务,如果当前事务已存在,则当前事务被挂起,待新事务结束后原有事务恢复继续执行。

       NOT_SUPPORTED:以非事务环境执行,如果当前事务已存在,则当前事务被挂起,待方法结束后原有事务恢复继续执行。

       NEVER:以非事务环境执行,如果当前事务已存在,则抛出异常。

       NESTED:如果当前事务已存在,则创建一个事务作为当前事务的嵌套事务执行,否则等价于REQUIRED。嵌套事务是外部事务的一部分,如果外部事务commit时,嵌套事务也会被提交,rollback同理。

       2、事务隔离级别,指的是多个并发事务之间的隔离程度。TransactionDefinition定义了5种隔离级别,我们使用Isolation枚举来指定,分别是:DEFAULT、READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ、SERIALIZABLE。Spring默认的隔离级别为DEFAULT。除DEFAULT外,其它四种分别对应数据库的事务隔离级别。

在说各隔离级别之前我们来看下在并发事务情况下可能发生的几种异常情况

       脏读(Dirty read):发生在一个事务读取了另一个事务改写但还未提交的数据。如果这些改变在稍后被回滚,那么之前的事务读取到的数据就是无效的。

       不可重复读(Nonrepeatable read):发生在一个事务先后执行相同的查询两次或两次以上,但每一次的查询结果不同。这通常是由于另一个并发的事务在两次查询之间更新了数据。

       幻读(Phantom read):发生在一个事务读取几行记录后,另一个事务插入了一些记录。在后来的查询中第一个事务就会发现有一些原来没有的额外的记录。

下面是各事务隔离级别的解释:

     DEFAULT:使用底层数据库默认的事务隔离级别,对于MySQL/InnoDB来说就是REPEATABLE_READ,Oracle默认为READ_COMMITTED。

       READ_UNCOMMITTED(未提交读):允许读取改变了的还未提交的数据,可能导致脏读、不可重复读和幻读。

       READ_COMMITTED(已提交读):只能读取到已经提交的数据,可以避免脏读,可能导致重复读和幻读。

       REPEATABLE_READ(可重复读):在同一个事务内的查询都是事务开始时刻一致的,对相同字段的多次读取结果相同,该隔离级别消除了不可重复读,但是还存在幻象读。

       SERIALIZABLE(可串行化):完全串行化的读,每次读都需要获得锁,读写相互都会阻塞,确保不发生脏读、不可重复读和幻读。但性能损耗很大。

       3、事务超时,指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。以整型来设置超时时间,其单位是秒。默认为-1,即使用底层事务系统的超时时间,如果底层数据库没有设置超时时间,那么就没有超时限制。

       4、事务只读,表示一个事务当中只存在读,不存在写,默认为false。当一个事务是只读时,数据库可能会做一个优化处理,但在只读事务中进行数据更新可能会导致脏数据。

       5、事务回滚规则,表示事务在哪些情况下回滚,推荐方法是在当前事务的上下文内抛出异常,spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。

       上面是事务理论,下面是Spring事务的实现

       Spring的声明式事务是基于AOP来实现,而AOP又是基于动态代理实现。@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时我们也可以在方法级别使用该标注来覆盖类级别的定义。

       SpringBoot事务配置是由DataSourceTransactionManagerAutoConfiguration和TransactionAutoConfiguration来初始化的。其中TransactionAutoConfiguration是用于编程式事务TransactionTemplate。DataSourceTransactionManagerAutoConfiguration这个类主要做了两件事:一是创建DataSourceTransactionManager对象,该对象实现PlatformTransactionManager接口,持有DataSource以及管理事务的开启、提交、回滚、挂起等,是Spring事务管理的低层实现;二是@ EnableTransactionManagement开启基于注解的声明式事务,和XML里的<tx:annotation-driven/>一样的功能。下面我们来分别看看这两个类是怎么实现的:

       一、@EnableTransactionManagement声明式事务配置

       该注解会通过@Import(TransactionManagementConfigurationSelector.class)导入TransactionManagementConfigurationSelector类,并且proxyTargetClass属性会设置为true,默认的mode为AdviceMode.PROXY。TransactionManagementConfigurationSelector的selectImports方法会返回AutoProxyRegistrar以及ProxyTransactionManagementConfiguration这两个类名组成的数组。ConfigurationClassPostProcessor会对selectImports返回的类进行解析注册。

       AutoProxyRegistrar实现了ImportBeanDefinitionRegistrar接口,里面主要的功能是调用AopConfigUtils创建代理创建对象InfrastructureAdvisorAutoProxyCreator,但是在此之前AopAutoConfiguration已经创建了一个AnnotationAwareAspectJAutoProxyCreator代理创建对象,通过查看AopConfigUtils源码就会发现AnnotationAwareAspectJAutoProxyCreator的优先级是最高的,所以实际的代理实现类是AnnotationAwareAspectJAutoProxyCreator。

       ProxyTransactionManagementConfiguration主要是创建BeanFactoryTransactionAttributeSourceAdvisor、TransactionInterceptor以及AnnotationTransactionAttributeSource对象。AnnotationTransactionAttributeSource为事务源配置,基于注解@Transactional,支持Spring及标准注解,其中会把解析出的事务配置缓存到ConcurrentHashMap中。TransactionInterceptor是事务拦截器,实现了MethodInterceptor接口,用于对AOP代理对象的方法进行包装处理,会拦截事务方法的调用,该类调用PlatformTransactionManager控制事务的开启、提交、回滚等执行流程,此类会把当前事务信息TransactionInfo(持有数据库连接)放入线程变量ThreadLocal中,并持有外层事务信息,当前事务处理完后会还原外层事务,这样就可以实现事务嵌套处理。BeanFactoryTransactionAttributeSourceAdvisor实现了PointcutAdvisor接口,AOP创建代理类的时候会通过该对象来设置事务的切入点及拦截器。

       AnnotationAwareAspectJAutoProxyCreator用于创建切面代理,当然也包括创建事务代理,它实现了BeanPostProcessor接口,会使用所有PointcutAdvisor实现类对BeanFactory中的实例进行判断是否有切入点,有的话就会织入事务控制程序。(BeanPostProcessor用于在Spring容器完成Bean的实例化、配置和其他的初始化后添加一些自己的逻辑处理,而BeanFactoryPostProcessor是在容器实际实例化bean之前读取配置元数据并可以修改配置)Spring中通过CGLIB给目标类生成子类的方式创建代理类,然后重写目标类的方法,关于生成代理类不在本文的讨论范围。Spring注入进来的Bean一般都是代理类,我们一般情况下调用方法都是通过代理类来调用真正的目标方法的。但如果是类内部的方法调用,是直接调用的目标方法,而不经过代理类,因为Spring事务是基于代理类来实现,所以只有通过代理类的调用事务才会生效,类内部的方法调用事务是不生效的,即使被调用方法使用@Transactional注解进行修饰,这是由AOP的性质所决定的。

       二、DataSourceTransactionManager事务管理器

       主要围绕PlatformTransactionManager接口的三个方法(getTransaction、commit、rollback)来实现,该类继承AbstractPlatformTransactionManager,其中采用模板模式封装了三个事务处理方法的基本逻辑。对于getTransaction,会先判断事务是否存在,再结合事务的传播行为执行不同的处理逻辑,如创建新事务、挂起事务等。对于commit,会先检查事务是否是readOnly,如果是,则回滚,否则提交事务。

       DataSourceTransactionManager是基于DataSource的事务管理,Spring会通过DataSource获取数据库连接Connection,并把它放到ThreadLocal线程变量中,事务的管理控制最终都是交给Connection的相关方法来完成。Spring必须要保证在同一个事务中的Connection是相同的,事务才会正确生效。Spring为了不同ORM框架(Hibernate,MyBatis)中获取的Connection都是相同的,并且统一管理连接的打开、关闭,Spring会代理DataSource.getConnection方法,代理方法会从ThreadLocal中获取一个经过代理的当前的Connection对象。

0 0
原创粉丝点击