spring的事务传播属性REQUIRED_NESTED的原理介绍

spring的事务传播属性required_nested的原理介绍

本文讲解"spring的事务传播属性required_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的原理介绍" 就介绍到此。希望多多支持硕编程

下一节:解决springboot全局异常处理与aop日志处理中@afterthrowing失效问题的方法

java编程技术

相关文章