Spring 声明式事务中使用注解@Transactional的方式、原理及注意事项
本文主要讨论Spring声明式事务中使用注解@Transactional的方式、原理及注意事项,主要包括以下内容:
- Spring @Transactional的配置使用;
- Spring @Transactional的传播行为和隔离级别;
- Spring @Transactional的工作原理;
- Spring @Transactional的注意事项;
- Spring @Transactional自我调用中的问题。
1、Spring @Transactional的配置
步骤一、在Spring配置文件中引入命名空间
1 | <beans xmlns="http://www.springframework.org/schema/beans" |
步骤二、xml配置文件中,添加事务管理器bean配置
<!-- 事务管理器配置,单数据源事务 -->
<bean id="pkgouTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="pkGouDataSource" />
</bean>
<!-- 使用annotation定义事务 -->
<tx:annotation-driven transaction-manager="pkgouTransactionManager" />
步骤三、在使用事务的方法或者类上添加 @Transactional(“pkgouTransactionManager”)注解
2、 Spring @Transactional的传播行为和隔离级别
1> 事务注解方式: @Transactional
- 标注在类前:标示类中所有方法都进行事务处理
- 标注在接口、实现类的方法前:标示方法进行事务处理
2> 事务传播行为介绍:
事务传播行为 | 说明 |
---|---|
@Transactional(propagation=Propagation.REQUIRED) | 如果有事务, 那么加入事务, 没有的话新建一个(默认情况) |
@Transactional(propagation=Propagation.NOT_SUPPORTED) | 容器不为这个方法开启事务 |
@Transactional(propagation=Propagation.REQUIRES_NEW) | 不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务 |
@Transactional(propagation=Propagation.MANDATORY) | 必须在一个已有的事务中执行,否则抛出异常 |
@Transactional(propagation=Propagation.NEVER) | 必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反) |
@Transactional(propagation=Propagation.SUPPORTS) | 如果其他bean调用这个方法,在其他bean中声明事务,那就用事务。如果其他bean没有声明事务,那就不用事务 |
3> 事务超时设置:
@Transactional(timeout=30) //默认是30秒
4> 事务隔离级别:
事务隔离级别 | 说明 |
---|---|
@Transactional(isolation = Isolation.READ_UNCOMMITTED) | 读取未提交数据(会出现脏读, 不可重复读),基本不使用 |
@Transactional(isolation = Isolation.READ_COMMITTED)(SQLSERVER默认) | 读取已提交数据(会出现不可重复读和幻读) |
@Transactional(isolation = Isolation.REPEATABLE_READ) | 可重复读(会出现幻读) |
@Transactional(isolation = Isolation.SERIALIZABLE) | 串行化 |
- 脏读 : 一个事务读取到另一事务未提交的更新数据
- 不可重复读 : 在同一事务中, 多次读取同一数据返回的结果有所不同, 换句话说, 后续读取可以读到另一事务已提交的更新数据。相反,”可重复读”在同一事务中多次读取数据时,能够保证所读数据一样,也就是后续读取不能读到另一事务已提交的更新数据
- 幻读 : 一个事务读到另一个事务已提交的insert数据
@Transactional的属性:
3、 Spring @Transactional的工作原理
- 自动提交
默认情况下,数据库处于自动提交模式。每一条语句处于一个单独的事务中,在这条语句执行完毕时,如果执行成功则隐式的提交事务,如果执行失败则隐式的回滚事务。
事务管理,是一组相关的操作处于一个事务之中,因此必须关闭数据库的自动提交模式。这点,Spring会在org/springframework/jdbc/datasource/DataSourceTransactionManager.java中将底层连接的自动提交特性设置为false。
1 | // switch to manual commit if necessary。 this is very expensive in some jdbc drivers, |
- spring事务回滚规则
Spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。Spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。
默认配置下,Spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚)。而抛出checked异常则不会导致事务回滚。
Spring也支持明确的配置在抛出哪些异常时回滚事务,包括checked异常。也可以明确定义哪些异常抛出时不回滚事务。
还可以编程性的通过setRollbackOnly()方法来指示一个事务必须回滚,在调用完setRollbackOnly()后你所能执行的唯一操作就是回滚。
在上面,我们使用了声明式事务来配置事务,使事务配置从service逻辑处理中解耦出来。但它还存在一些缺点:
1. 我们只针对方法名的特定进行拦截,但无法利用方法签名的其它信息定位,如修饰符、返回值、方法入参、异常类型等。如果我们需要为同名不同参的同载方法配置不同事务就会出问题了。
2. 事务属性的配置串虽然能包含较多信息,但配置较易出错。
针对这些问题,我们可以基于Schema,引入tx和aop的命名空间来改进我们的配置:
- 引入命名空间
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd">
- tx\aop核心配置
<!-- 配置事务属性 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="add*" propagation="REQUIRES_NEW" /> <tx:method name="update*" propagation="REQUIRES_NEW" /> <tx:method name="delete*" propagation="REQUIRES_NEW" /> <tx:method name="*" read-only="true"/> </tx:attributes> </tx:advice> <!-- 配置事务切入点,以及把事务切入点和事务属性关联起来 --> <aop:config proxy-target-class="true"> <aop:pointcut expression="execution(* com.yc.service.*.*(..))" id="ServicePointcut" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="ServicePointcut" /> </aop:config>
这里需要特别注意的是,我们需要在标签中将proxy-target-class配置成true,否则会出现和上一篇文章相同的错误:我们定义的类无法转换成代理类
这里我们通过来配置我们的事务增强属性。在标签中,常见属性及其说明如下,其中,除了name属性是必选外,其他都是可选的:
属性 | 说明 | 默认 | 允许值 |
---|---|---|---|
name | 匹配方法名 | 必须声明,至少为* | 可使用*通配符 |
propagation | 事务传播行为 | REQUIRED | REQUIRED,SUPPORTS和MANDATORY和REQUIRES_NEW和NOT_SUPPORTED和NEVER和NESTED |
read-only | 设置当前事务是否只读 | false | true,false |
isolation | 事务隔离级别 | DEFAULT | READ_UNCOMMITTED和READ_COMMITTED和REPEATABLE_READ和SERIALIZABLE |
timeout | 设置事务的超时时间 | -1 | 默认由顶层事务系统决定 |
rollback-for | 内容为异常名,表示当抛出这些异常时事务回滚,可以用逗号分隔配置多个 | 无默认值 | 可以使用异常名称的片段进行匹配如ception等 |
no-rollback-for | 内容为异常名,表示当抛出这些异常时继续提交事务,可以用逗号分隔配置多个 | 无默认值 | 可以使用异常名称的片段进行匹配如ception等。 |