Spring事务失效的 8 大原因,与事物传播行为对比测试!!!

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唯一的不同之处)。

总结

相同点:
  1. NESTED与REQUIRES_NEW两个事物,在事务中抛出运行时异常的时候,在REQUIRED中没有try…catch捕获异常的情况下,都会引起REQUIRED事物跟着一起回滚。
  2. NESTED与REQUIRES_NEW两个事物,在事务中抛出运行时异常的时候,如果在REQUIRED中try…catch捕获了异常的情况下,都只会使自己的事物进行回滚,并不会引起REQUIRED事物跟着一起回滚。
不同点:
  1. REQUIRES_NEW事物是先将REQUIRED事物挂起后,又开启的新事务,彼此之间没有联系。如果REQUIRED事务中抛异常导致回滚的话,不会导致REQUIRES_NEW事务也跟着回滚。
  2. NESTED事务是在REQUIRED事务中创建的子事务,彼此之间是父子关系。如果REQUIRED事务中抛异常导致回滚的话,NESTED事务也会跟着回滚。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值