Spring框架中的事务
有很多人觉得我们有了Spring,就再也不需要去处理获得连接、事务提交、回滚和关闭连接等这些操作了,其实并不是这样的,事实上Spring并不是直接管理事务的,只是提供了多种事务管理器,让持久化机制所提供的平台框架的事务来实现事务管理。
Spring事务管理的三大接口底层的实现关系如图所示:
三者的关系非常清晰,TransactionDefinition 将 Transaction 传给 PlatformTransactionManager ,TransactionStatus 又从PlatformTransactionManager 得到Transaction 。
具体的PlatformTransactionManager实现类又有4种,它们是针对不同的数据库或者框架而定制的。包括:
- DataSourceTransactionManager
- HibernateTransactionManager
- JpaTransactionManager
- JtaTranscationManager
Spring事务管理的API
Spring事务管理主要包括3个接口,Spring的事务主要是由他们三个共同完成的,分别是:PlatformTransactionManager,TransactionDefinition,TransactionStatus。其中的PlatformTransactionManager是Spring事务管理的核心接口!这三个事务管理器接口是根据指定的传播行为,返回当前活动的事务,或者创建一个新的事务,参数的类定义一些基本的事务属性。
PlatformTransactionManager
第一个接口是PlatformTransactionManager,是Spring事务管理的核心接口。主要功能是事务管理器,是用于平台相关事务的管理,包括commit 事务的提交;rollback 事务的回滚;getTransaction 事务状态的获取三种方法。
TransactionDefinition
第二个接口是TransactionDefinition,主要功能是事务定义信息,是用来定义事务相关的属性,给事务管理器PlatformTransactionManager使用的。而且在TransactionDefinition接口中定义了它自己的传播行为和隔离级别。包括getIsolationLevel:获取隔离级别;getPropagationBehavior:获取传播行为;getTimeout:获取超时时间;isReadOnly:是否只读 四种方法。
TransactionStatus
第三个接口是TransactionStatus,主要功能是事务具体运行状态,是事务管理过程中,每个时间点事务的状态信息,它可以封装许多代码,节省我们的工作量。包括hasSavepoint():返回这个事务内部是否包含一个保存点;isCompleted():返回该事务是否已完成,也就是说,是否已经提交或回滚;isNewTransaction():判断当前事务是否是一个新事务 这三种方法。
Spring如何配置事务管理器
看完以上的三个接口,那么你知道Spring是如何配置事务管理器的吗?
1. 编程式事务管理
通过PlatformTransactionManager实现来进行事务管理。这种方式在实际开发中,很少使用,基本都是使用声明式事务管理。
<!--配置事务管理的模板-->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transacationManager />
<property name="isolationLevelName" value="ISOLATION_DEFAULT" />
<property name="propagationBeHaviorName" value="PROPAGETION_REQUIRED" />
</bean>
2. 声明式事务管理
声明式事务管理有又有两种不同的方式:一种是基于tx和aop命名空间的xml配置文件,也称配置Aspectj方式的声明式事务,另一种是使用注解的声明式事务
Aspectj方式的声明式事务
步骤:
- maven依赖
1-1 spring-aop
1-2 spring-aspects
1-3 aspectjweaver - spring-root.xml配置文件中引入命名空间
- 配置Aspectj方法的声明式事务、
3-1 配置事务管理器
3-2 配置事务通知
3-3 配置aop:config [切入点、通知]
1.maven依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.2</version>
</dependency>
2. spring-root配置文件引入命名空间
需要使用的命名空间有tx和aop。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
">
3.配置Aspectj方式的声明式事务
- 配置事务管理器
- 配置事务通知
- 配置aop:config (切入点 通知)
具体代码:
<!-- 事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManag er">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置事务通知 -->
<tx:advice id="tx" transaction-manager="transactionManager">
<tx:attributes>
<!-- name:表示以什么开头的方法 -->
<!-- propagation:传播行为 -->
<!-- isolation:隔离级别 -->
<tx:method name="add*" propagation="REQUIRED" isolation="DEFAULT" />
<tx:method name="insert*" propagation="REQUIRED" isolation="DEFAULT" />
<tx:method name="update*" propagation="REQUIRED" isolation="DEFAULT" />
<tx:method name="delete*" propagation="REQUIRED" isolation="DEFAULT" />
<tx:method name="del*" propagation="REQUIRED" isolation="DEFAULT" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="tx-point" expression="execution(* com.golden3young.service..*.*(..))" />
<aop:advisor advice-ref="tx" pointcut-ref="tx-point" />
</aop:config>
关于代码中的execution(* com.golden3young.service….(…)),解释一下是什么意思。这是一个表达式:开头的 * 表示 任意方法权限修饰符, com.golden3young.service… 表示com.golden3young.service包及其所有子包下的内容,至于是什么内容,由紧接着的 *. *(…)来指定,意思是任意类型下的任意方法。(…)表示任意的方法参数
重点就是 一个 * 表示当前层级的所有 , 两个 * 表示当前层级和所有子层级
使用注解方式的声明式事务
步骤:
- 在spring-root.xml配置文件中配置事务管理器 (不需要配置事务通知)
- 直接在代码使用事务的地方写Transaction注解即可
1.配置事务管理器
<!-- 事务管理器 -->
<bean id="tx" class="org.springframework.jdbc.datasource.DataSourceTransactionManag er">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置注解扫描驱动,用于自动扫描写在代码中的注解 -->
<tx:annotation-driven transaction-manager="tx" />
根据配置的代码可以看出来,依赖于命名空间tx,所以也需要提前引入。
2.在代码中直接使用注解
@Transactional
@Override
public int addUser(User user) {
return userMapper.addUser(user);
}
例如,在Service层中调用dao层方法的时候,我们就可以直接使用@Transactional注解,Spring容器将自动进行扫描,为我们构建对应的事务管理。
知道了事务具体的配置方法,那么下面我们再来研究一下,在配置过程中,可以配置的参数都用哪些,以及对应着哪些功能。
spring事务的隔离级别
spring提供了五种隔离级:
- ISOLATION_DEFAULT:默认的 这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是 ISOLATION_READ_COMMITTED。
- ISOLATION_READ_UNCOMMITTED:未提交读 该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。 该级别不能防止脏读、不可重复读和幻读,因此很少使用该隔离级别。
- ISOLATION_READ_COMMITTED:已提交读 该隔离级别表示一个事务只能读取另一个事务已经提交的数据。 该级别可以防止脏读,这也是大多数情况下的推荐值。
- ISOLATION_REPEATABLE_READ:可重复读 该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相 同。 该级别可以防止脏读、不可重复读。
- ISOLATION_SERIALIZABLE:序列化 所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰; 也就是说,该级别可以防止脏读、不可重复读以及幻读。 但是这将严重影响程序的性能。通常情况下也不会用到该级别。
spring事务的传播行为
当多个具有事务的方法直接存在依赖关系时,事务是如何传播的呢?我们可以进行设置。
- REQUIRED:表示如果当前存在一个事务,则加入该事务,否则将新建一个事务;
解释:
method A(){
methodB();
}
method B(){
}
如果A方法和B方法的事务传播行为都是 REQUIRED 等级,方法A中调用了方法B,那么如果A和B都有事务,B将加入到A的事务中。如果A没有事务,而B有事务,B将创建自己的事务。
- REQUIRES_NEW:表示不管是否存在事务,都创建一个新的事务,原来的挂起,新 的执行完毕,继续执行老的事务;
method A(){
methodB();
}
method B(){}
如果A\B都有事务 --B 将暂停A的事务
如果A没有事务,B有事务 ---B 将创建自己的事务
- SUPPORTS:表示如果当前存在事务,就加入该事务;如果当前没有事务,那就不使 用事务;
method A(){
methodB();
}
method B(){}
如果A\B都有事务 --B 将加入到A 的事务中
如果A没有事务,B有事务 ---B 将不使用任何事务
- NOT_SUPPORTED: 表示不使用事务;如果当前存在事务,就把当前事务暂停,以 非事务方式执行;
method A(){ //
methodB();
}
method B(){}
不管A有没有事务,B都不用。A如果有,B就给它暂停,如果没有那正好。
- MANDATORY:表示必须在一个已有的事务中执行,如果当前没有事务,则抛出异常;
method A(){
methodB();
}
method B(){ //PROPAGATION_MANDATORY
}
如果方法B的事务传播等级为MANDATORY
那它必须以事务的方式运行
调用的地方必须要有事务,如果没有事务,B将直接抛异常
- NEVER:表示以非事务方式执行,如果当前存在事务,则抛出异常;
method A(){ //
methodB();
}
method B(){ //
}
必须以没有事务的方式运行
调用的地方必须没有事务,如果有事务,B将直接抛异常
- NESTED:这个是嵌套事务; 如果当前存在事务,则在嵌套事务内执行; 如果当前不存在事务,则创建一个新的事务;
嵌套事务使用数据库中的保存点来实现,即嵌套事务回滚不影响外部事务,但外部事务回滚将导致嵌套事务回滚;
学到了事务中具体的属性和属性值,那么我们的事务配置就可以进行自定义的指定了。例如注解可以这样书写:@Transacational(propagation=Propagetion.REQUIRED,isolation = Isolation.DEFAULT)
也就是在注解的括号中,增加了propagation属性和isolation属性并赋值。
spring事务的回滚机制
Java的世界里,有两种异常:运行时异常和非运行时异常。根据名字即可轻松的辨别出它们的区别。
运行时异常是在代码运行过程中出现的例外情况。运行时异常是不需要捕获的,程序员可以不去处理,当异常出现时,虚拟机会处理,当然如果虚拟机处理不了,最终还是得程序员去处理。
非运行时异常(也叫checked异常)就必须得捕获了,否则连编译都不过去,java编译器要求程序员必须对这种异常进行catch,Java认为Checked异常都是可以被处理(修复)的异常,所以Java程序必须显式处理Checked异常。
在处理非运行时异常时,有两种处理办法:一种是抛给上级,一种是try-catch自己消化掉。
在spring事务的回滚机制中,其作用就是当指定的方法抛出异常时,会触发事务的回滚机制,来将刚才对数据库的修改进行回滚。
- spring事务默认只对运行时异常(非检查型异常)起作用
- spring可以指定需要处理的异常类型,使用rollbackFor指定需要处理的异常
- spring也可以指定不需要处理的异常,使用noRollbackFor指定不需要处理的异常;
默认情况下,Spring只有在抛出的异常是运行时异常(“非检查型”)时才回滚该事务; 也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚); 而抛出非运行时异常(检查型)则不会导致事务回滚; 但是,我们可以明确的配置抛出哪些异常时回滚事务,包括checked异常。也可以定义哪些异常抛出时不回滚事务。
我们再来将刚才那个注解,添加上回滚机制中的属性和属性值:
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, rollbackFor = Exception.class, noRollbackFor = XxxExcetion.class )
总结
所谓Spring事务,也就是将数据库中的事务放到了Spring中,其实Spring是整合了一下各种数据库中的事务,将不同数据库中的事务进行总结,这样,一旦使用了Spring框架,就可以直接将事务管理交给Spring来处理,不再需要像之前面向数据库来自己处理事务。既然数据库的种类繁多,各数据库中的事务种类又各不相同,所以在整合过程中,就存在着多种情况,这就是为什么在本文一开头就介绍了Spring事务的底层接口和实现类,以及它们直接是如何进行工作的。随后,由于事务处理中,有各种属性,如:隔离级别、传播行为、回滚机制,所以,我们在使用spring管理事务的时候,可以通过属性+属性值来进行设置,剩下的工作就全部交给Spring去帮助我们完成。配置Spring事务的方法又有两种,编程式和声明式,一般在实际开发中,使用声明式居多,同时,Aspectj方式在springmvc中使用较多,注解方式在springboot中使用较多。不过,这都不是固定的,在掌握了不同的配置方法后,如何配置完全由自己决定,当然,我们都偏爱简洁迅速的方式。