spring的实务失效问题?,和事物的传播行为问题?,在我们的工作中经常困扰着我们!!!遇到的时候要花很多心思!过后没几天就又抛之脑后了!为了在今后的工作中可以节省点解决这个问题的时间,特此花了些时间整理了这篇文章!!!
既然使用了事物,那肯定需要先配置事物,那我们就先回顾一下声明式事物的配置。这里先做个配置事物的简单记录。
【Spring】——声明式事务配置
概念—Spring中的事务管理
Spring在不同的事务管理API之上定义了一个抽象层,使得开发人员不必了解底层的事务管理API就可以使用Spring的事务管理机制。Spring支持编程式事务管理和声明式的事务管理。
编程式事务管理
将事务管理代码嵌到业务方法中来控制事务的提交和回滚
缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码
声明式事务管理
一般情况下比编程式事务好用。将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式事务管理。
事务管理器
Spring的核心事务管理抽象,管理封装了一组独立于技术的方法,无论使用Spring的哪种事务管理策略(编程式或者声明式)事务管理器都是必须的。
org.springframework.transaction.PlatformTransactionManager
有如下几种:
JDBC事务
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
Hibernate事务
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
JPA事务
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
Java原生API事务
通常用于跨越多个事务管理源(多数据源),则需要使用下面的内容
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManagerName" value="java:/TransactionManager" />
</bean>
使用—声明式管理事务
使用声明式管理事物有两种方式,第二种配置简单使用居多。
1通知声明式地管理事务
<!-- 1. 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 2. 配置事务属性 -->
<!--<tx:advice>元素声明事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 根据方法名指定事务的属性 -->
<tx:method name="*"/>
<!--propagation配置事务传播行为-->
<tx:method name="purchase" propagation="REQUIRES_NEW"/>
<!--isolation配置事务的隔离级别-->
<tx:method name="update*" isolation="SERIALIZABLE"/>
<!--rollback-for配置事务遇到异常必须回滚,no-rollback-for配置事务遇到异常必须不能回滚-->
<tx:method name="add*" rollback-for="java.io.IOException" no-rollback-for="com.dmsd.spring.tx.BookStockException"/>
<!--read-only配置事务只读属性-->
<tx:method name="find*" read-only="true"/>
<!--timeout配置事务的超时属性-->
<tx:method name="get*" timeout="3"/>
</tx:attributes>
</tx:advice>
<!-- 3. 配置事务切入点, 以及把事务切入点和事务属性关联起来 -->
<aop:config>
<aop:pointcut expression="execution(* com.atguigu.spring.tx.xml.service.*.*(..))"
id="txPointCut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
2用 @Transactional 注解声明式地管理事务
Spring中注解的方式@Transactional标注事务方法。为了将方法定义为支持事务处理,可以在方法上添加@Transactional注解。根据Spring AOP基于代理机制,只能标注公有方法。如果在类上标注@Transactional注解,那么这个类中所有公有方法都会被定义为支持事务。
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 启用事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
然后在方法上添加@Transactional注解就可以使用了
//添加事务注解
//1.使用 propagation 指定事务的传播行为, 即当前的事务方法被另外一个事务方法调用时
//如何使用事务, 默认取值为 REQUIRED, 即使用调用方法的事务
//REQUIRES_NEW: 事务自己的事务, 调用的事务方法的事务被挂起.
//2.使用 isolation 指定事务的隔离级别, 最常用的取值为 READ_COMMITTED
//3.默认情况下 Spring 的声明式事务对所有的运行时异常进行回滚. 也可以通过对应的
//属性进行设置. 通常情况下去默认值即可.
//4.使用 readOnly 指定事务是否为只读. 表示这个事务只读取数据但不更新数据,
//这样可以帮助数据库引擎优化事务. 若真的事一个只读取数据库值的方法, 应设置 readOnly=true
//5.使用 timeout 指定强制回滚之前事务可以占用的时间.
@Transactional(propagation=Propagation.REQUIRES_NEW,
isolation=Isolation.READ_COMMITTED,
noRollbackFor={UserAccountException.class},
rollbackFor = IOException.class,
readOnly=false,
timeout=3)
@Override
public void purchase(String username, String isbn) {}
这样我们的事物就配置好了。接下来我们研究一下在使用事物过程中,导致事物失效的几个原因。
Spring事务失效的 8 大原因
1、数据库引擎不支持事务
这里以 MySQL 为例,其 MyISAM 引擎是不支持事务操作的,InnoDB 才是支持事务的引擎,一般要支持事务都会使用 InnoDB。
根据 MySQL 的官方文档:
https://dev.mysql.com/doc/ref...
从 MySQL 5.5.5 开始的默认存储引擎是:InnoDB,之前默认的都是:MyISAM,所以这点要值得注意,底层引擎不支持事务再怎么搞都是白搭。
2、没有被 Spring 管理
如下面例子所示:
// @Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void updateOrder(Order order) {
// update order
}
}
如果此时把 @Service 注解注释掉,这个类就不会被加载成一个 Bean,那这个类就不会被 Spring 管理了,事务自然就失效了。
3、方法不是 public 的
以下来自 Spring 官方文档:
When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.
大概意思就是 @Transactional 只能用于 public 的方法上,否则事务就会失效,如果要用在非 public 方法上,可以开启 AspectJ 代理模式。
4、不支持事务
来看下面这个例子:
@Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void update(Order order) {
updateOrder(order);
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void updateOrder(Order order) {
// update order
}
}
Propagation.NOT_SUPPORTED: 表示不以事务运行,当前若存在事务则挂起,详细的可以参考《事务隔离级别和传播机制》这篇文章。
都主动不支持以事务方式运行了,那事务生效也是白搭!
5、数据源没有配置事务管理器
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
或
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
如上面所示,当前数据源若没有配置事务管理器,那也是白搭!
6、异常类型错误
上面的例子再抛出一个异常:
// @Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void updateOrder(Order order) {
try {
// update order
} catch {
throw new Exception("更新错误");
}
}
}
这样事务也是不生效的,因为默认回滚的是:RuntimeException,如果你想触发其他异常的回滚,需要在注解上配置一下,如:
@Transactional(rollbackFor = Exception.class)
这个配置仅限于 Throwable 异常类及其子类。
7、异常被吃了
这个也是出现比较多的场景:
// @Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void updateOrder(Order order) {
try {
// update order
} catch {
}
}
}
把异常吃了,然后就不抛出来,事务怎么回滚吧!
8、自身调用问题
来看三个示例:
例子①-1
@Service
public class OrderServiceImpl implements OrderService {
public void update(Order order) {
insertA();//插入一条订单A数据
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());//打印当前事务名称
updateOrder(order);
}
@Transactional
public void updateOrder(Order order) {
insertB();//插入一条订单B数据
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());//打印当前事务名称
int a = 10/0;//制造运行时RuntimeException异常,测试事物回滚
}
}
update方法上面没有加 @Transactional 注解,调用有 @Transactional 注解的 updateOrder 方法,updateOrder 方法上的事务管用吗?
运行结果是:控制台打印了当前事务的两个名称都为null,还有异常信息,并且事物没有回滚(数据库order订单表中有A和B两条订单信息)。说明没有开启事物
例子①-2
@Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void update(Order order) {
insertA();//插入一条订单A数据
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());//打印当前事务名称
updateOrder(order);
}
public void updateOrder(Order order) {
insertB();//插入一条订单B数据
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());//打印当前事务名称
int a = 10/0;//制造运行时RuntimeException异常,测试事物回滚
}
}
update方法上面添加了 @Transactional 注解,调用没有 @Transactional 注解的 updateOrder 方法,updateOrder 方法上的事务管用吗?
运行结果是:控制台打印了当前事务的两个名称都为update,还有异常信息,并且事物回滚了(数据库中既没有数据A,也没有数据B)。说明开启了事物,并且,update方法与updateOrder方法在同一个事物中
再来看下面这个例子:
例子②
@Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void update(Order order) {
insertA();//插入一条订单A数据
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());//打印当前事务名称
updateOrder(order);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateOrder(Order order) {
insertB();//插入一条订单B数据
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());//打印当前事务名称
int a = 10/0;//制造运行时RuntimeException异常,测试事物回滚
}
}
这次在 update 方法上加了 @Transactional,updateOrder 加了 REQUIRES_NEW 新开启一个事务,那么新开的事务管用么?
运行结果是:控制台打印了当前事务的两个名称都为update,还有异常信息,事物回滚了(数据库中既没有数据A,也没有数据B,这与我们了解的事物传播属性REQUIRES_NEW不符)。说明两个方法使用的是同一个事物(REQUIRES_NEW没起作用)
再来看这个例子:
例子③
@Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void update(Order order) {
insertA();//插入一条订单A数据
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());//打印当前事务名称
updateOrder(order);
}
@Transactional
public void updateOrder(Order order) {
insertB();//插入一条订单B数据
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());//打印当前事务名称
int a = 10/0;//制造运行时RuntimeException异常,测试事物回滚
}
}
运行结果是:控制台打印了当前事务的两个名称都为update,还有异常信息,事物回滚了(数据库中既没有数据A,也没有数据B,这与我们了解的事物传播属性REQUIRED似乎相同。但真的相同吗?并不是,只是表现出来的现象与我们想的一致而已,这个隐藏的比较深需要我们根据深入的测试!!!)。
这三个例子的事物都是:不管用的!(尤其是第三例子,我们下面会针对测试!!!)
因为它们发生了自身调用,就调该类自己的方法,而没有经过 Spring 的代理类,默认只有在外部调用事务才会生效,这也是老生常谈的经典问题了。
如何实现呢?这个的解决方案之一就是在的类中注入自己,用注入的对象再调用另外一个方法,这个不太优雅,另外一个可行的方案可以参考《Spring 如何在一个事务中开启另一个事务?》这篇文章。
修改例子①
@Service
public class OrderServiceImpl implements OrderService {
OrderService orderService;
//在OrderServiceImpl类中获取OrderService接口实例,并操作OrderService实例插入。
@PostConstruct
public void init() {
orderService = context.getBean(OrderService.class);
}
public void update(Order order) {
insertA();//插入一条订单A数据
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());//打印当前事务名称
orderService.updateOrder(order);
}
@Transactional
public void updateOrder(Order order) {
insertB();//插入一条订单B数据
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());//打印当前事务名称
int a = 10/0;//制造运行时RuntimeException异常,测试事物回滚
}
}
运行结果是:控制台打印了当前事务的两个名称一个为null,另一个为updateOrder,还有异常信息,并且事物updateOrder发生回滚(数据库order订单表中只有A一条订单信息)。说明开启了事物
修改例子②
@Service
public class OrderServiceImpl implements OrderService {
OrderService orderService;
//在OrderServiceImpl类中获取OrderService接口实例,并操作OrderService实例插入。
@PostConstruct
public void init() {
orderService = context.getBean(OrderService.class);
}
@Transactional
public void update(Order order) {
insertA();//插入一条订单A数据
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());//打印当前事务名称
orderService.updateOrder(order);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateOrder(Order order) {
insertB();//插入一条订单B数据
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());//打印当前事务名称
int a = 10/0;//制造运行时RuntimeException异常,测试事物回滚
}
}
运行结果是:控制台打印了当前事务的两个名称分别为update和updateOrder,还有异常信息,事物回滚了(数据库order订单表中只有A一条订单信息)。说明两个方法使用的是两个不同的事物
修改例子③
@Service
public class OrderServiceImpl implements OrderService {
OrderService orderService;
//在OrderServiceImpl类中获取OrderService接口实例,并操作OrderService实例插入。
@PostConstruct
public void init() {
orderService = context.getBean(OrderService.class);
}
@Transactional
public void update(Order order) {
insertA();//插入一条订单A数据
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());//打印当前事务名称
orderService.updateOrder(order);
}
@Transactional
public void updateOrder(Order order) {
insertB();//插入一条订单B数据
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());//打印当前事务名称
int a = 10/0;//制造运行时RuntimeException异常,测试事物回滚
}
}
运行结果是:控制台打印了当前事务的两个名称都为update,还有异常信息,事物回滚了(数据库中既没有数据A,也没有数据B,这样上边例子③的现象一样呀?怎么回事???上边我们已经说了,例子③只是表现的与正确的使用方式相同,但其实还是错误的,因为使用了this。想要发现问题,还需要深入的测试!!!)。
在例子③中添加try…catch…捕获异常
@Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void update(Order order) {
insertA();//插入一条订单A数据
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());//打印当前事务名称
try {
updateOrder(order);
} catch (Exception e) {
}
}
@Transactional
public void updateOrder(Order order) {
insertB();//插入一条订单B数据
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());//打印当前事务名称
int a = 10/0;//制造运行时RuntimeException异常,测试事物回滚
}
}
运行结果是:控制台打印了当前事务的两个名称都为update,没有打印异常信息,事物没有回滚(数据库中有数据A和B两条数据,这是因为我们吧异常catch吃了吗?并不是,其实是因为在调用updateOrder的时候因为使用this的缘故,并没有使用代理的事物)。
正确的现象应该是下面这样。↓↓↓↓↓↓↓
在修改例子③中添加try…catch…捕获异常
@Service
public class OrderServiceImpl implements OrderService {
OrderService orderService;
//在OrderServiceImpl类中获取OrderService接口实例,并操作OrderService实例插入。
@PostConstruct
public void init() {
orderService = context.getBean(OrderService.class);
}
@Transactional
public void update(Order order) {
insertA();//插入一条订单A数据
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());//打印当前事务名称
try {
orderService.updateOrder(order);
} catch (Exception e) {
}
}
@Transactional
public void updateOrder(Order order) {
insertB();//插入一条订单B数据
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());//打印当前事务名称
int a = 10/0;//制造运行时RuntimeException异常,测试事物回滚
}
}
运行结果是:控制台打印了当前事务的两个名称都为update,还有异常信息,事物发生了回滚(数据库中既没有数据A,也没有数据B。怎么样?是不是有点晕?为什么我们catch吃了异常函数updateOrder后还是会打印异常信息,并且事物发生回滚了呢?这是因为我们使用了代理后的orderService对象的事物方法updateOrder,updateOrder发生异常并在控制台打印后spring捕获、触发了事物的回滚,然后继续向上抛出这个异常,最后才是被我们的catch捕获)。
按照上面的思路,我们在updateOrder里面去catch吃掉异常,是不是就不会在控制台打印异常,也不会发生事物回滚呢?
我们再来继续测试。。
在修改例子③中 修改添加try…catch…捕获异常的位置
@Service
public class OrderServiceImpl implements OrderService {
OrderService orderService;
//在OrderServiceImpl类中获取OrderService接口实例,并操作OrderService实例插入。
@PostConstruct
public void init() {
orderService = context.getBean(OrderService.class);
}
@Transactional
public void update(Order order) {
insertA();//插入一条订单A数据
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());//打印当前事务名称
orderService.updateOrder(order);
}
@Transactional
public void updateOrder(Order order) {
try {
insertB();//插入一条订单B数据
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());//打印当前事务名称
int a = 10/0;//制造运行时RuntimeException异常,测试事物回滚
} catch (Exception e) {
}
}
}
运行结果是:控制台打印了当前事务的两个名称都为update,并没有在控制台中打印异常信息,事物也没有发生回滚(数据库中有数据A和B两条数据。怎么样?是不是与我们预测的一样呀!!!因为我们在使用了代理后的orderService对象的事物方法updateOrder中catch吃掉了异常,所以就不会抛出异常信息被spring捕获,也就不会导致事物回滚了!!!控制台也就不会打印异常信息)。
这下各位应该知道例子①②③中的问题出现在哪里了吧?归根结底都是自调用(this)惹的祸。所以同学们一定要记住呀。以后在使用声明式事物的时候,**千万,千万,千万不要使用自调用呀!!!**因为这会给我们带来很多奇怪的问题。
总结
本文总结了八种事务失效的场景,其实发生最多就是自身调用、异常被吃、异常抛出类型不对这三个了。
spring 嵌套事务(Nested) 和新建事务(REQUIRES_NEW)对比测试
spring事物传播行为
事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如方法可能继续在现有事务中运行,也可能开启一个新的事务,并在自己的事务运行。spring中的事务传播行为可以由传播属性指定。spring指定了7种类传播行为。
REQUIRED | 如果有事务在运行,当前的方法就在这个事务内运行,否则就开启一个新的事务,并在自己的事务内运行,(也是默认的传播行为) |
---|---|
REQUIRED_NEW | 当前方法必须启动新事务,并在自己的事务内运行,如果有事务正在运行,则将它挂起 |
SUPPORTS | 如果有事务在运行,当前的方法就在这个事务内运行,否则可以不运行在事务中 |
NOT_SUPPORTED | 表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager |
MANDATORY | 当前的方法必须运行在事务内部,如果没有正在运行的事务,就会抛出异常 |
NEVER | 当前方法不应该运行在事务中,如果有运行的事务,就抛出异常 |
NESTED | 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。 |
通常情况下,第一种和第二种用的比较多,需要多理解一下。还有就是人们经常拿来比较的第二种REQUIRED_NEW与第七种NESTED的区别?
接下来我们针对REQUIRED_NEW和NESTED这两种传播行为进行测试:
我们直接使用上面的代码进行修改,保证事务功能正确开启(避免使用this自调用的情况)为前提。
REQUIRED_NEW
不添加try…catch…捕获异常
@Service
public class OrderServiceImpl implements OrderService {
OrderService orderService;
//在OrderServiceImpl类中获取OrderService接口实例,并操作OrderService实例插入。
@PostConstruct
public void init() {
orderService = context.getBean(OrderService.class);
}
@Transactional
public void update(Order order) {
insertA();//插入一条订单A数据
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());//打印当前事务名称
orderService.updateOrder(order);
}
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void updateOrder(Order order) {
insertB();//插入一条订单B数据
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());//打印当前事务名称
int a = 10/0;//制造运行时RuntimeException异常,测试事物回滚
}
}
运行结果是:控制台打印了当前事务的两个名称分别为update与updateOrder,还有异常信息,事物回滚了(数据库中既没有数据A,也没有数据B,这与REQUIRES_NEW传播行为的描述不一样呀?怎么回事???根据事务名称判断,明明开启了两个事物,那为什么updateOrder事物抛异常、回滚后,update事物也跟着回滚了呢?我们在上面测试事物失效原因的时候了解到,事务中抛出的异常,因为我们自己没有捕获,而是被spring捕获的话,就会触发事物回滚。那是不是这个原因呢?接下来继续测试)。
添加try…catch…捕获异常
@Service
public class OrderServiceImpl implements OrderService {
OrderService orderService;
//在OrderServiceImpl类中获取OrderService接口实例,并操作OrderService实例插入。
@PostConstruct
public void init() {
orderService = context.getBean(OrderService.class);
}
@Transactional
public void update(Order order) {
insertA();//插入一条订单A数据
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());//打印当前事务名称
try {
orderService.updateOrder(order);
} catch (Exception e) {
}
}
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void updateOrder(Order order) {
insertB();//插入一条订单B数据
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());//打印当前事务名称
int a = 10/0;//制造运行时RuntimeException异常,测试事物回滚
}
}
运行结果是:控制台打印了当前事务的两个名称分别为update与updateOrder,没有打印异常信息,事物发生了回滚(数据库中只有数据A,没有数据B。果然,与我们的猜想一样。我们在事务update中捕获事务updateOrder中抛出的异常后。发现只有新事务updateOrder发生了回滚,update事务并没有回滚。这样的话就与传播行为REQUIRES_NEW的描述相同了。原来:REQUIRES_NEW传播行为开启的新事务在发生异常导致回滚时,为了不影响原来的事务也跟着回滚,是需要在原来的事务中使用try…catch捕获异常的呀!!!)。
在REQUIRED事务中模拟异常
@Service
public class OrderServiceImpl implements OrderService {
OrderService orderService;
//在OrderServiceImpl类中获取OrderService接口实例,并操作OrderService实例插入。
@PostConstruct
public void init() {
orderService = context.getBean(OrderService.class);
}
@Transactional
public void update(Order order) {
insertA();//插入一条订单A数据
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());//打印当前事务名称
orderService.updateOrder(order);
int a = 10/0;//制造运行时RuntimeException异常,测试事物回滚
}
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void updateOrder(Order order) {
insertB();//插入一条订单B数据
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());//打印当前事务名称
//int a = 10/0;//制造运行时RuntimeException异常,测试事物回滚
}
}
运行结果是:控制台打印了当前事务的两个名称分别为update与updateOrder,还有异常信息,事物回滚了(数据库中没有数据A,只有数据B。REQUIRES_NEW事务并没有因为REQUIRED的事务中抛出异常后事务发生回滚而回滚。两个事物在使用try…catch的情况下,可以做到互不影响。这与REQUIRES_NEW传播行为的描述一样)。
NESTED
不添加try…catch…捕获异常
@Service
public class OrderServiceImpl implements OrderService {
OrderService orderService;
//在OrderServiceImpl类中获取OrderService接口实例,并操作OrderService实例插入。
@PostConstruct
public void init() {
orderService = context.getBean(OrderService.class);
}
@Transactional
public void update(Order order) {
insertA();//插入一条订单A数据
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());//打印当前事务名称
orderService.updateOrder(order);
}
@Transactional(propagation=Propagation.NESTED)
public void updateOrder(Order order) {
insertB();//插入一条订单B数据
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());//打印当前事务名称
int a = 10/0;//制造运行时RuntimeException异常,测试事物回滚
}
}
运行结果是:控制台打印了当前事务的两个名称都为update,还有异常信息,事物回滚了(数据库中既没有数据A,也没有数据B。这与REQUIRES_NEW传播行为差不多呀?不同之处就是NESTED行为在代码中两处打印当前事务名称时,控制台打印的都是同一个事物update。而REQUIRES_NEW行为打印的是两个,分别是update事物和updateOrder事物。为什么会这样呢?看看NESTED的描述“NESTED行为的事物会作为当前事务的嵌套”,我们可以理解为“NESTED行为的事物会作为当前事务的子事务,只是可以单独提交和回滚”,所以他们两个的事物名字才相同)。
添加try…catch…捕获异常
@Service
public class OrderServiceImpl implements OrderService {
OrderService orderService;
//在OrderServiceImpl类中获取OrderService接口实例,并操作OrderService实例插入。
@PostConstruct
public void init() {
orderService = context.getBean(OrderService.class);
}
@Transactional
public void update(Order order) {
insertA();//插入一条订单A数据
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());//打印当前事务名称
try {
orderService.updateOrder(order);
} catch (Exception e) {
}
}
@Transactional(propagation=Propagation.NESTED)
public void updateOrder(Order order) {
insertB();//插入一条订单B数据
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());//打印当前事务名称
int a = 10/0;//制造运行时RuntimeException异常,测试事物回滚
}
}
运行结果是:控制台打印了当前事务的两个名称都是update,没有打印异常信息,事物发生了回滚(数据库中只有数据A,没有数据B。这与传播行为REQUIRES_NEW的现象相同)。
在REQUIRED事务中模拟异常
@Service
public class OrderServiceImpl implements OrderService {
OrderService orderService;
//在OrderServiceImpl类中获取OrderService接口实例,并操作OrderService实例插入。
@PostConstruct
public void init() {
orderService = context.getBean(OrderService.class);
}
@Transactional
public void update(Order order) {
insertA();//插入一条订单A数据
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());//打印当前事务名称
orderService.updateOrder(order);
int a = 10/0;//制造运行时RuntimeException异常,测试事物回滚
}
@Transactional(propagation=Propagation.NESTED)
public void updateOrder(Order order) {
insertB();//插入一条订单B数据
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());//打印当前事务名称
//int a = 10/0;//制造运行时RuntimeException异常,测试事物回滚
}
}
运行结果是:控制台打印了当前事务的两个名称都是update,还有异常信息,事物回滚了(数据库中既没有数据A,也没有数据B。在主事物update中抛出异常并回滚的情况下,子事务updateOrder也会跟着回滚。这就是与NESTED与REQUIRES_NEW唯一的不同之处)。
总结
相同点:
- NESTED与REQUIRES_NEW两个事物,在事务中抛出运行时异常的时候,在REQUIRED中没有try…catch捕获异常的情况下,都会引起REQUIRED事物跟着一起回滚。
- NESTED与REQUIRES_NEW两个事物,在事务中抛出运行时异常的时候,如果在REQUIRED中try…catch捕获了异常的情况下,都只会使自己的事物进行回滚,并不会引起REQUIRED事物跟着一起回滚。
不同点:
- REQUIRES_NEW事物是先将REQUIRED事物挂起后,又开启的新事务,彼此之间没有联系。如果REQUIRED事务中抛异常导致回滚的话,不会导致REQUIRES_NEW事务也跟着回滚。
- NESTED事务是在REQUIRED事务中创建的子事务,彼此之间是父子关系。如果REQUIRED事务中抛异常导致回滚的话,NESTED事务也会跟着回滚。