spring的事务传播属性REQUIRED_NESTED原理

 更新时间:2023年05月15日 08:52:34   作者:morris131  
这篇文章主要介绍了spring的事务传播属性REQUIRED_NESTED原理,在spring中,要想使用事务中的回滚点,可以使用传播属性NESTED,需要的朋友可以参考下

传统事务中回滚点的使用

package com.morris.spring.demo.jdbc;
import java.sql.*;
/**
 * 传统JDBC中回滚点的使用
 */
public class TraditionSavePointDemo {
	public static void main(String[] args) throws SQLException {
		String url = "jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&allowMultiQueries=true&characterEncoding=UTF-8&useFastDateParsing=false&zeroDateTimeBehavior=convertToNull";
		String username = "user";
		String password = "user";
		Connection connection = DriverManager.getConnection(url, username, password);
		connection.setAutoCommit(false); // 不自动提交
		Savepoint one = connection.setSavepoint("one");
		Savepoint two = null;
		try {
			Statement statement = connection.createStatement();
			statement.execute("insert into t_good(good_name, price) values('iphone14', 9999)");
			statement.close();
			two = connection.setSavepoint("two");
		} catch (Exception e) {
			e.printStackTrace();
			connection.rollback(one); // 回滚事务
		}
		try {
			Statement statement = connection.createStatement();
			statement.execute("insert into t_good(good_name, price) values('iphone15', 9999)");
			statement.close();
			boolean flag = true;
			if(flag) {
				throw new RuntimeException("xxxx");
			}
		} catch (Exception e) {
			e.printStackTrace();
			connection.rollback(two); // 回滚事务
		}
		connection.commit();
	}
}

在一个事务中可以指定回滚事务到某一个阶段,实现精确控制事务。

事务的传播属性NESTED

在spring中,要想使用事务中的回滚点,可以使用传播属性NESTED。

com.morris.spring.service.TransactionService#addGoodAndArea

@Transactional(propagation = Propagation.REQUIRED)
public void addGoodAndArea() {
	System.out.println("------addGoodAndArea-------");
	goodService.addGood();
	areaService.addArea(0);
}

com.morris.spring.service.AreaServiceImpl#addArea

@Transactional(propagation = Propagation.NESTED)
@Override
public boolean addArea(int i) {
	int y = 1000000 / i;
	Area area = new Area();
	area.setAreaCode(y);
	area.setAreaName("shenzhen");
	return areaDao.insert(area);
}

com.morris.spring.service.GoodServiceImpl#addGood

@Transactional(propagation = Propagation.NESTED)
@Override
public boolean addGood() {
	Good good = new Good();
	good.setGoodName("iphone");
	good.setPrice(BigDecimal.valueOf(99999));
	return goodDao.insert(good);
}

运行结果如下:

DEBUG DataSourceTransactionManager:384 - Creating new transaction with name [com.morris.spring.service.TransactionService.addGoodAndArea]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
DEBUG DriverManagerDataSource:144 - Creating new JDBC DriverManager Connection to [jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&allowMultiQueries=true&characterEncoding=UTF-8&useFastDateParsing=false&zeroDateTimeBehavior=convertToNull]
DEBUG DataSourceTransactionManager:267 - Acquired Connection [com.mysql.cj.jdbc.ConnectionImpl@8ef162] for JDBC transaction
DEBUG DataSourceTransactionManager:285 - Switching JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@8ef162] to manual commit
------addGoodAndArea-------
DEBUG DataSourceTransactionManager:477 - Creating nested transaction with name [com.morris.spring.service.GoodServiceImpl.addGood]
DEBUG JdbcTemplate:860 - Executing prepared SQL update
DEBUG JdbcTemplate:609 - Executing prepared SQL statement [insert into t_good(good_name, price) values(?,?)]
DEBUG DataSourceTransactionManager:767 - Releasing transaction savepoint
DEBUG DataSourceTransactionManager:477 - Creating nested transaction with name [com.morris.spring.service.AreaServiceImpl.addArea]
DEBUG DataSourceTransactionManager:870 - Rolling back transaction to savepoint
DEBUG DataSourceTransactionManager:877 - Initiating transaction rollback
DEBUG DataSourceTransactionManager:347 - Rolling back JDBC transaction on Connection [com.mysql.cj.jdbc.ConnectionImpl@8ef162]
DEBUG DataSourceTransactionManager:392 - Releasing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@8ef162] after transaction
java.lang.ArithmeticException: / by zero
... ...

发现整个事务都已经回滚了,按照回滚点的逻辑,addArea()方法抛出异常,不是应该只回滚到addArea()前吗,也就是addGood()应该被提交,这是为什么呢?

如果我们将addArea()方法try catch起来,就能得到我们想要的结果,addGood()被提交,而addArea()回滚,这又是为什么呢?我们带着这几个问题来分析源码。

addAreaAndGood()开启事务

addAreaAndGood()开启事务,最外层方法使用传播属性PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW、PROPAGATION_NESTED效果都一样,都是开启一个新的事务。

org.springframework.transaction.support.AbstractPlatformTransactionManager#getTransaction

else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
		def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
		def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
	// 第一次进来
	SuspendedResourcesHolder suspendedResources = suspend(null);
	if (debugEnabled) {
		logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
	}
	try {
		// 开启新事务
		return startTransaction(def, transaction, debugEnabled, suspendedResources);
	}
	catch (RuntimeException | Error ex) {
		resume(null, suspendedResources);
		throw ex;
	}
}

addGood()获得事务并创建回滚点

addGood()从ThreadLocal中获得addAreaAndGood()创建的事务,然后发现自己的传播属性为PROPAGATION_NESTED,就创建了一个回滚点。

org.springframework.transaction.support.AbstractPlatformTransactionManager#handleExistingTransaction

if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
	if (!isNestedTransactionAllowed()) {
		throw new NestedTransactionNotSupportedException(
				"Transaction manager does not allow nested transactions by default - " +
				"specify 'nestedTransactionAllowed' property with value 'true'");
	}
	if (debugEnabled) {
		logger.debug("Creating nested transaction with name [" + definition.getName() + "]");
	}
	if (useSavepointForNestedTransaction()) {
		// Create savepoint within existing Spring-managed transaction,
		// through the SavepointManager API implemented by TransactionStatus.
		// Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.
		DefaultTransactionStatus status =
				prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
		// 创建回滚点
		status.createAndHoldSavepoint();
		return status;
	}
	else {
		// Nested transaction through nested begin and commit/rollback calls.
		// Usually only for JTA: Spring synchronization might get activated here
		// in case of a pre-existing JTA transaction.
		return startTransaction(definition, transaction, debugEnabled, null);
	}
}

addGood()提交事务时释放回滚点

addGood()并不会真正的提交事务,因为事务并不是addGood()创建的,只是在提交时会将之前创建的回滚点释放。

org.springframework.transaction.support.AbstractPlatformTransactionManager#processCommit

if (status.hasSavepoint()) {
	// NESTED的提交
	if (status.isDebug()) {
		logger.debug("Releasing transaction savepoint");
	}
	unexpectedRollback = status.isGlobalRollbackOnly();
	// 只是释放回滚点
	status.releaseHeldSavepoint();
}

addArea()获得事务并创建回滚点

流程与addGood()一致。

addArea()回滚事务释放回滚点

addArea()发生异常,会执行回滚事务的逻辑,并没有真正的回滚事务,因为事务并不是addArea()创建的,,只是将之前创建的回滚点释放。 org.springframework.transaction.support.AbstractPlatformTransactionManager#processRollback

if (status.hasSavepoint()) {
	// 用于NESTED传播机制,发生异常
	// 回滚至回滚点
	if (status.isDebug()) {
		logger.debug("Rolling back transaction to savepoint");
	}
	status.rollbackToHeldSavepoint();
}

addAreaAndGood()回滚这个事务

addArea()发生异常后继续往外抛,addAreaAndGood()也会捕获到异常,然后执行回滚逻辑,这样整个事务都回滚了。 org.springframework.transaction.support.AbstractPlatformTransactionManager#processRollback

else if (status.isNewTransaction()) {
	// 只有最外层的事务newTransaction=true
	if (status.isDebug()) {
		logger.debug("Initiating transaction rollback");
	}
	// 事务的回滚
	/**
	 * @see org.springframework.jdbc.datasource.DataSourceTransactionManager#doRollback(org.springframework.transaction.support.DefaultTransactionStatus)
	 */
	doRollback(status);
}

为什么将addArea()方法try catch起来,整个事务就不会回滚了呢?

因为将addArea()方法try catch起来后,addAreaAndGood()就会执行提交事务的逻辑,这样addGood()就被提交了。

到此这篇关于spring的事务传播属性REQUIRED_NESTED原理的文章就介绍到这了,更多相关spring REQUIRED_NESTED原理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 利用POI生成EXCEL文件的方法实例

    利用POI生成EXCEL文件的方法实例

    Apache POI 是用Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java程式对Microsoft Office格式档案读和写的功能,下面这篇文章主要给大家介绍了关于利用POI生成EXCEL文件的相关资料,需要的朋友可以参考下
    2018-07-07
  • Java数据结构学习之二叉树

    Java数据结构学习之二叉树

    今天给大家带来的是关于Java数据结构的相关知识,文章围绕着Java二叉树展开,文中有非常详细的介绍及代码示例,需要的朋友可以参考下
    2021-06-06
  • java分布式流式处理组件Producer分区理论

    java分布式流式处理组件Producer分区理论

    这篇文章主要为大家介绍了java分布式流式处理组件Producer分区理论详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-03
  • java在pdf中生成表格的方法

    java在pdf中生成表格的方法

    这篇文章主要介绍了java在pdf中生成表格的方法,需要的朋友可以参考下
    2015-11-11
  • Java 全面掌握网络编程篇

    Java 全面掌握网络编程篇

    网络编程是指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来。java.net 包中 J2SE 的 API 包含有类和接口,它们提供低层次的通信细节。你可以直接使用这些类和接口,来专注于解决问题,而不用关注通信细节
    2021-10-10
  • Maven项目部署到Jboss出现Failed to create a new SAX parser

    Maven项目部署到Jboss出现Failed to create a new SAX parser

    这篇文章主要为大家详细介绍了Maven项目部署到Jboss出现Failed to create a new SAX parser的解决方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-11-11
  • 基于javamelody监控springboot项目过程详解

    基于javamelody监控springboot项目过程详解

    这篇文章主要介绍了基于javamelody监控springboot项目过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11
  • Java快速排序案例讲解

    Java快速排序案例讲解

    这篇文章主要介绍了Java快速排序案例讲解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-08-08
  • Intellij Idea中进行Mybatis逆向工程的实现

    Intellij Idea中进行Mybatis逆向工程的实现

    这篇文章主要介绍了Intellij Idea中进行Mybatis逆向工程的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-05-05
  • Java中Set与List的关系与区别介绍

    Java中Set与List的关系与区别介绍

    这篇文章主要介绍了Java中Set与List的关系与区别介绍,本文总结它们两个接口都是继承自Collection、它们之间的存储方式不一样,需要的朋友可以参考下
    2015-03-03

最新评论