Use and implementation principles of Spring Transaction transactions


preface

The data of a service system usually ends up in a database, such as MySQL and Oracle. It is inevitable that errors may occur during data update. In this case, you need to withdraw the previous data update operations to avoid incorrect data.

SpringDeclarative transactions can help us handle rollback operations without having to worry about transactions at the bottom of the database, and without having to write rollback operations in try/catch/finaly in case of an exception.

SpringThe degree of transaction assurance is greater than other technologies in the industry (e.gTCC / 2PC / 3PCEtc.) slightly weaker, but usedSpringTransactions already cover most scenarios, so its usage and configuration rules are worth learning as well.

Let’s learn how Spring transactions are used and implemented.


Using the example

1. Create a database table

create table test.user(
id int auto_increment	
primary key.name varchar(20) null, age int(3) null)
engine=InnoDB charset=utf8;
Copy the code

2. Create the PO for the database table

public class JdbcUser {

	private Integer id;

	private String name;

	privateInteger age; . Complete setter and getter with CTRL + N}Copy the code

3. Create mappings between tables and entities

In the use ofJdbcTemplateIs very tangled, inJavaThere’s a lot of hard coding in the classSQL, andMyBatisUse method is not the same, for the sake of simple example, usedJdbcTemplateHowever, I recommend it to my friendsMyBatisKeep the code style clean.

public class UserRowMapper implements RowMapper {


	@Override
	public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
		JdbcUser user = new JdbcUser();
		user.setId(rs.getInt("id"));
		user.setName(rs.getString("name"));
		user.setAge(rs.getInt("age"));
		returnuser; }}Copy the code

4. Create a data operation interface

public interface UserDao {

	/** * insert *@paramUser User information */
	void insertUser(JdbcUser user);

	/** * Delete by id *@paramId primary key * /
	void deleteById(Integer id);

	/** * query *@returnAll * /
	List<JdbcUser> selectAll(a);
}
Copy the code

5. Create a data operation interface implementation class

Unlike the examples in the book, transaction annotations are not added to the interface, but to public methods, where you can customize propagation events and isolation levels on each method.

public class UserJdbcTemplate implements UserDao {

	private DataSource dataSource;

	private JdbcTemplate jdbcTemplate;


	@Override
	@Transactional(propagation = Propagation.REQUIRED)
	public void insertUser(JdbcUser user) {
		String sql = "insert into user (id, name, age) values (? ,? ,?) ";
		jdbcTemplate.update(sql, user.getId(), user.getName(), user.getAge());
		System.out.println("Create record : " + user.toString());
	}

	@Override
	@Transactional
	public void deleteById(Integer id) {
		String sql = "delete from user where id = ?";
		jdbcTemplate.update(sql, id);
		System.out.println("Delete record, id = " + id);
		// Transaction test, throw an exception to roll back the above insert operation
		throw new RuntimeException("aa");
	}


	@Override
	public List<JdbcUser> selectAll(a) {
		String sql = "select * from user";
		List<JdbcUser> users = jdbcTemplate.query(sql, new UserRowMapper());
		return users;
	}



	public void setDataSource(DataSource dataSource) {
		// Initialize the jdbcTemplate when using setter to inject parameters
		this.dataSource = dataSource;
		this.jdbcTemplate = newJdbcTemplate(dataSource); }}Copy the code

6. Create a configuration file

<?xml version="1.0" encoding="UTF-8"? >
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xmlns:tx="http://www.springframework.org/schema/tx"
	   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.xsd">

	<! MySQL -->
	<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
		<property name="url" value="jdbc:mysql://localhost:3306/test? characterEncoding=utf8"/>
		<property name="username" value="root"/>
		<property name="password" value="12345678"/>
	</bean>

	<bean id="userJdbcTemplate" class="transaction.UserJdbcTemplate">
		<property name="dataSource" ref="dataSource"/>
	</bean>

	<! If this line is removed, the transaction will not be created -->
	<tx:annotation-driven/>
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"/>
	</bean>
</beans>
Copy the code

7. Add dependencies

Remember to add database connections and spring module dependencies for JDBC and TX

optional(project(":spring-jdbc"))  // for Quartz support
optional(project(":spring-tx"))  // for Quartz support
compile group: 'mysql', name: 'mysql-connector-java', version: '5.1.6'
Copy the code

8. Startup code

public class TransactionBootstrap {

	public static void main(String[] args) {
			ApplicationContext context = new ClassPathXmlApplicationContext("transaction/transaction.xml");
			UserJdbcTemplate jdbcTemplate = (UserJdbcTemplate) context.getBean("userJdbcTemplate");
			System.out.println("--- Records Creation start ----");
			JdbcUser user = new JdbcUser(4."test".21); jdbcTemplate.insertUser(user); }}Copy the code

With the above code, I did two tests:

  1. Transaction is not enabled in configuration file.That is<tx:annotation-driven/>This line is commented out, even though it is thrown in the method we executeRuntimeExcepton, but data is still being inserted into the database.
  2. In the configuration file, start transactions.Delete the above comment, delete the records in the database, re-execute the startup code, and find that the data is not inserted. In the case of an exception thrown by the program,SpringThe transaction was successfully executed and the insert operation was rolled back.

The annotation attribute @Transactional

Specific location:org.springframework.transaction.annotation.Transactional

attribute type role
value String Optional qualified descriptor that specifies the transaction manager to use
propagation Enumeration: Propagation Optional transaction propagation behavior
isolation Enumeration: the Isolation Optional transaction isolation level setting
readOnly boolean Set read-write or read-only transactions. Default is read-only
rollbackFor Class array, must inherit from Throwable An array of exception classes that cause the transaction to roll back
rollbackForClassName An array of class names that must be inherited from Throwable
noRollbackFor Class array, must inherit from Throwable An array of exception classes that do not cause the transaction to roll back
noRollbackForClassName An array of class names that must be inherited from Throwable

Propagation of transactions

  • REQUIRED

This is the default propagation property. If the external caller has a transaction, it will join it, if not, it will create a new one.

  • PROPAGATION_SUPPORTS

If a transaction exists, join the transaction. If there is no transaction currently, it continues in a non-transactional manner.

  • PROPAGATION_NOT_SUPPORTED

Runs nontransactionally and suspends the current transaction if one exists.

  • PROPAGATION_NEVER

Runs nontransactionally and throws an exception if a transaction currently exists.


Isolation Isolation of transactions

  • READ_UNCOMMITTED

At the lowest level, only physically damaged data can be read and dirty read is allowed

  • READ_COMMITTED

Only committed data can be read

  • REPEATABLE_READ

Repeatable read

  • SERIALIZABLE

Serialized reads, where reads and writes block each other

This is just a brief description of the two main properties, because the underlying properties are database related. Take a look at the MySQL lock mechanism I cleaned up earlier


Implement logic in Spring

After the introduction of how to use and key attribute Settings, in the spirit of learning, know why, to understand how the code is implemented.


parsing

AOP and TX both use custom tags. We will use transaction custom tags to resolve custom tags.

Locate the initialization method of the TxNamespaceHandler class:

@Override
public void init(a) {
	registerBeanDefinitionParser("advice".new TxAdviceBeanDefinitionParser());
	/ / use AnnotationDrivenBeanDefinitionParser parser, parsing the annotation - driven label
	registerBeanDefinitionParser("annotation-driven".new AnnotationDrivenBeanDefinitionParser());
	registerBeanDefinitionParser("jta-transaction-manager".new JtaTransactionManagerBeanDefinitionParser());
}
Copy the code

According to the above methods, the Spring at initialization time, if you encounter such as < tx: annotation – driven > at the beginning of configuration, will use AnnotationDrivenBeanDefinitionParser parser parses the parse method.

public BeanDefinition parse(Element element, ParserContext parserContext) {
	registerTransactionalEventListenerFactory(parserContext);
	String mode = element.getAttribute("mode");
	// AspectJ handles this separately
	if ("aspectj".equals(mode)) {
		// mode="aspectj"
		registerTransactionAspect(element, parserContext);
		if (ClassUtils.isPresent("javax.transaction.Transactional", getClass().getClassLoader())) { registerJtaTransactionAspect(element, parserContext); }}else {
		// mode="proxy"
		AopAutoProxyConfigurer.configureAutoProxyCreator(element, parserContext);
	}
	return null;
}
Copy the code

Transactions in Spring are AOP based by default. If you want to use AspectJ for transaction entry, you need to configure this in the mode property:

<tx:annotation-driven mode="aspectj"/>
Copy the code

This note focuses on the default implementation, dynamic AOP, for more information if you are interested in the AspectJ implementation


Registered InfrastructureAdvisorAutoProxyCreator

With AOP, when parsing, creates a automatically create proxies, the transaction is the TX module, use InfrastructureAdvisorAutoProxyCreator.

First of all, in case the default configuration, AopAutoProxyConfigurer. ConfigureAutoProxyCreator (element, parserContext) did what action:

private static class AopAutoProxyConfigurer {
	public static void configureAutoProxyCreator(Element element, ParserContext parserContext) {
		/ / registered InfrastructureAdvisorAutoProxyCreator automatically create proxies
		AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);
		// txAdvisorBeanName = org.springframework.transaction.config.internalTransactionAdvisor
		String txAdvisorBeanName = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME;
		if(! parserContext.getRegistry().containsBeanDefinition(txAdvisorBeanName)) { Object eleSource = parserContext.extractSource(element);// Create the TransactionAttributeSource definition.
			/ / create TransactionAttributeSource bean
			RootBeanDefinition sourceDef = new RootBeanDefinition(
					"org.springframework.transaction.annotation.AnnotationTransactionAttributeSource");
			// Register the bean and generate the beanName using the rules defined in Spring
			String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef);
			// Create a TransactionInterceptor bean
			RootBeanDefinition interceptorDef = new RootBeanDefinition(TransactionInterceptor.class);
			interceptorDef.getPropertyValues().add("transactionAttributeSource".new RuntimeBeanReference(sourceName));
			String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef);
			/ / create TransactionAttributeSourceAdvisor bean
			RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryTransactionAttributeSourceAdvisor.class);
			/ / will sourceName bean into advisor transactionAttributeSource attributes
			advisorDef.getPropertyValues().add("transactionAttributeSource".new RuntimeBeanReference(sourceName));
			// Inject the interceptorName bean into the adviceBeanName property of the Advisor
			advisorDef.getPropertyValues().add("adviceBeanName", interceptorName);
			if (element.hasAttribute("order")) {
				// If the order attribute is configured, it is added to the bean
				advisorDef.getPropertyValues().add("order", element.getAttribute("order"));
			}
			// Register advisorDef as txAdvisorBeanName
			parserContext.getRegistry().registerBeanDefinition(txAdvisorBeanName, advisorDef);
			/ / create CompositeComponentDefinition
			CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), eleSource);
			compositeDef.addNestedComponent(new BeanComponentDefinition(sourceDef, sourceName));
			compositeDef.addNestedComponent(new BeanComponentDefinition(interceptorDef, interceptorName));
			compositeDef.addNestedComponent(newBeanComponentDefinition(advisorDef, txAdvisorBeanName)); parserContext.registerComponent(compositeDef); }}}Copy the code

The proxy class and three are registered herebeanThese three keysbeanTo better understand the relationship between these three aspects, let’s review them firstAOPCore concepts:

  1. The Pointcut defines a Pointcut at which the cutting logic can be performed before and after the intercepted method.
  2. AdviceIt is used to define interception behavior and implements the enhanced logic here, which is an ancestor interfaceorg.aopalliance.aop.Advice. There are other inheritance interfaces, for exampleMethodBeforeAdvice, specifically refers to the enhancement before the method is executed.
  3. AdvisorIt is used to encapsulate all information about the aspect, mainly the above two, which are used to act asAdvicePointcutAdapter.

With AOP concepts reviewed, let’s move on to these three key beans:

  • TransactionInterceptor: to achieve theAdviceInterface, where interception behavior is defined.
  • AnnotationTransactionAttributeSource: encapsulates, though not implemented, the logic of whether the target method is interceptedPointcutInterface, but it is actually delegated to the target method later onAnnotationTransactionAttributeSource.getTransactionAttributeSource, through the adapter mode, returnsPointcutPointcut information.
  • TransactionAttributeSourceAdvisor: to achieve theAdvisorInterface that wraps the above two pieces of information.

The threebeanThe composition of the structure withAOPThe section surround implementation is structurally consistent, so learn firstAOPAn understanding of transactions can be helpful


Let’s see how our auto-create agent is created:

AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element)

public static void registerAutoProxyCreatorIfNecessary( ParserContext parserContext, Element sourceElement) {
	BeanDefinition beanDefinition = AopConfigUtils.registerAutoProxyCreatorIfNecessary(
			parserContext.getRegistry(), parserContext.extractSource(sourceElement));
	useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
	registerComponentIfNecessary(beanDefinition, parserContext);
}

private static void registerComponentIfNecessary(@Nullable BeanDefinition beanDefinition, ParserContext parserContext) {
	if(beanDefinition ! =null) {
	    / / registered beanName org. Springframework. Aop. Config. InternalAutoProxyCreator
		parserContext.registerComponent(
				newBeanComponentDefinition(beanDefinition, AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME)); }}Copy the code

In this step, one is registeredbeanNameorg.springframework.aop.config.internalAutoProxyCreatorbean:InfrastructureAdsivorAutoProxyCreator, the following is its inheritance system diagram:

And as you can see, it doesInstantiationAwareBeanPostProcessorThis interface, in other words, atSpringIn the container, all of thembeanWhen instantiating,SpringAre guaranteed to call itpostProcessAfterInitializationMethods.

With an introduction on AOP proxies, at the time of instantiation bean, call the agent is the parent class AbstractAutoProxyCreator postProcessAfterInitialization method:

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
	if(bean ! =null) {
		/ / assembly key
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		if (this.earlyProxyReferences.remove(cacheKey) ! = bean) {// Encapsulate the specified bean if it is suitable to be proxied
			returnwrapIfNecessary(bean, beanName, cacheKey); }}return bean;
}
Copy the code

The wrapIfNecessory method has been described in detail in the previous AOP article, and here’s what it does:

  1. Find the specifiedbeanCorresponding enhancer
  2. Create the agent based on the enhancer found

The process similar to creating an AOP proxy is not repeated, but is different:


Determine if the target method is suitable for canApply

AopUtils#canApply(Advisor, Class<? >, boolean)

public static boolean canApply(Advisor advisor, Class<? > targetClass,boolean hasIntroductions) {
	if (advisor instanceof IntroductionAdvisor) {
		return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
	}
	else if (advisor instanceof PointcutAdvisor) {
		PointcutAdvisor pca = (PointcutAdvisor) advisor;
		return canApply(pca.getPointcut(), targetClass, hasIntroductions);
	}
	else {
		// It doesn't have a pointcut so we assume it applies.
		return true; }}Copy the code

As we saw earlier,TransactionAttributeSourceAdvisorIs the parent classPointcutAdvisor, so when the target method is judging, it will take out the pointcut informationpca.getPointcut().

The type of slice we injected earlierbeanAnnotationTransactionAttributeSource, wrapped in the following method, returns an object of typeTransactionAttributeSourcePointcutPointcut information of

private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() {
	@Override
	@Nullable
	protected TransactionAttributeSource getTransactionAttributeSource(a) {
		/ / implementation of the parent class method in a subclass extends, before returning the label AnnotationTransactionAttributeSource when registering
		returntransactionAttributeSource; }};Copy the code

Tag match

In the matchmatchIn operation, the difference isAOPRecognition is@Before@AfterAnd our affairsTXRecognition is@TransactionalThe label.

The entry method to determine if it is a transaction method is here:

org.springframework.transaction.interceptor.TransactionAttributeSourcePointcut#matches

@Override
public boolean matches(Method method, Class
        targetClass) {
	// Transaction pointcut matching method
	TransactionAttributeSource tas = getTransactionAttributeSource();
	return (tas == null|| tas.getTransactionAttribute(method, targetClass) ! =null);
}
Copy the code

So how far does it go to parse transaction annotations? Keep tracing the code, and the answer is:

AnnotationTransactionAttributeSource#determineTransactionAttribute

protected TransactionAttribute determineTransactionAttribute(AnnotatedElement element) {
	for (TransactionAnnotationParser parser : this.annotationParsers) {
		TransactionAttribute attr = parser.parseTransactionAnnotation(element);
		if(attr ! =null) {
			returnattr; }}return null;
}
Copy the code

In this step, the registered annotation parser is iterated for parsing. Since we are concerned with transaction resolution, we directly locate the parser for the transaction annotation:

SpringTransactionAnnotationParser#parseTransactionAnnotation(AnnotatedElement)

public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {
	// Parse the properties of the transaction annotations
	AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
			element, Transactional.class, false.false);
	if(attributes ! =null) {
		return parseTransactionAnnotation(attributes);
	}
	else {
		return null; }}Copy the code

First check whether the @Transactional annotation exists, and if so, proceed to call the Parse method:

protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
	RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
	Annotation 9.4 Parses each attribute of the transaction annotation
	Propagation propagation = attributes.getEnum("propagation");
	rbta.setPropagationBehavior(propagation.value());
	Isolation isolation = attributes.getEnum("isolation");
	rbta.setIsolationLevel(isolation.value());
	rbta.setTimeout(attributes.getNumber("timeout").intValue());
	rbta.setReadOnly(attributes.getBoolean("readOnly"));
	rbta.setQualifier(attributes.getString("value"));
	List<RollbackRuleAttribute> rollbackRules = new ArrayList<>();
	for(Class<? > rbRule : attributes.getClassArray("rollbackFor")) {
		rollbackRules.add(new RollbackRuleAttribute(rbRule));
	}
	for (String rbRule : attributes.getStringArray("rollbackForClassName")) {
		rollbackRules.add(new RollbackRuleAttribute(rbRule));
	}
	for(Class<? > rbRule : attributes.getClassArray("noRollbackFor")) {
		rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
	}
	for (String rbRule : attributes.getStringArray("noRollbackForClassName")) {
		rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
	}
	rbta.setRollbackRules(rollbackRules);
	return rbta;
}
Copy the code

summary

Through the above steps, the transaction attribute resolution of the corresponding class or method is completed.

The main step is to find the enhancers and determine if they match the method or class.

If a bean belong to can be transaction, which is suitable for enhancer BeanFactoryTransactionAttributeSourceAdvisor enhanced.

Before we injectedTransactionInterceptorBeanFactoryTransactionAttributeSourceAdvisorIs executed when the transaction enhancer enhanced proxy class is calledTransactionInterceptorMake enhancements. At the same time, that isTransactionInterceptorIn the classinvokeMethod to complete the entire transaction.


run


The transaction enhancer TransactionInterceptor

The TransactionInterceptor supports the entire transaction architecture. As with AOP’s JDK dynamic proxy analysis, the TransactionInterceptor interceptor inherits from MethodInterceptor, so we’ll start with its key method invoke() :

public Object invoke(MethodInvocation invocation) throws Throwable {
	9.5 Execute the transaction interceptor to complete the logic of the entire transactionClass<? > targetClass = (invocation.getThis() ! =null ? AopUtils.getTargetClass(invocation.getThis()) : null);
	// Adapt to TransactionAspectSupport's invokeWithinTransaction...
	return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}
Copy the code

The parent class’s method is actually called:TransactionAspectSupport#invokeWithinTransaction

protected Object invokeWithinTransaction(Method method, @Nullable Class<? > targetClass,final InvocationCallback invocation) throws Throwable {
	// If the transaction attribute is null, the method is non-transactional
	TransactionAttributeSource tas = getTransactionAttributeSource();
	// Get the corresponding transaction attribute
	finalTransactionAttribute txAttr = (tas ! =null ? tas.getTransactionAttribute(method, targetClass) : null);
	// Get the transaction manager
	final PlatformTransactionManager tm = determineTransactionManager(txAttr);
	// The constructor uniquely identifies the class. Methods)
	final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
	if (txAttr == null| |! (tminstanceof CallbackPreferringPlatformTransactionManager)) {
		// Declarative transactions
		// Standard transaction partitioning: use getTransaction and COMMIT/rollback calls
		TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
		Object retVal;
		try {
			// The invocation is the callback object: Invocation. Proceed. Execute the enhanced method
			retVal = invocation.proceedWithInvocation();
		}
		catch (Throwable ex) {
			// Abnormal rollback
			completeTransactionAfterThrowing(txInfo, ex);
			throw ex;
		}
		finally {
			// Clear information
			cleanupTransactionInfo(txInfo);
		}
		// Commit the transaction
		commitTransactionAfterReturning(txInfo);
		return retVal;
	}
	else {
		// Programmatic transaction processing
		final ThrowableHolder throwableHolder = new ThrowableHolder();
		// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
		try{ Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> { TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); .returnresult; }}}Copy the code

The code posted has been deleted to simplify the try/catch of error exceptions and the logic of programmatic transaction handling. Because declarative Transactional processing, which is encoded in XML files or @Transactional annotations, is actually implemented using AOP, whereas programmatic Transactional processing, which is implemented using Transaction Template, is much less used. So this part of the processing code is omitted.


Transaction manager

This method determines the specific transaction manager to be used for a given transaction

TransactionAspectSupport#determineTransactionManager

protected PlatformTransactionManager determineTransactionManager(@Nullable TransactionAttribute txAttr) {
	// Do not attempt to lookup tx manager if no tx attributes are set
	// Comment 9.6 Finding a transaction manager
	if (txAttr == null || this.beanFactory == null) {
		// If there is no transaction attribute or the BeanFactory is empty, look in the cache
		return asPlatformTransactionManager(getTransactionManager());
	}

	String qualifier = txAttr.getQualifier();
	// If a transaction manager is specified in the annotation configuration, use it directly
	if (StringUtils.hasText(qualifier)) {
		return determineQualifiedTransactionManager(this.beanFactory, qualifier);
	}
	else if (StringUtils.hasText(this.transactionManagerBeanName)) {
		return determineQualifiedTransactionManager(this.beanFactory, this.transactionManagerBeanName);
	}
	else {
		// Select className from classNamePlatformTransactionManager defaultTransactionManager = asPlatformTransactionManager(getTransactionManager()); .returndefaultTransactionManager; }}Copy the code

Because at the beginning we have in the XML file configuration transactionManager attribute, so this method will return type in our example is the transaction manager DataSourceTransactionManager, It’s DataSourceTransactionManager inheritance system:

It implements the InitializingBean interface, but simply verifies that the dataSource is empty in the afterPropertiesSet() method without going into detail about the class.


The transaction open

TransactionAspectSupport#createTransactionIfNecessary

protected TransactionInfo createTransactionIfNecessary(PlatformTransactionManager tm, TransactionAttribute txAttr, final String joinpointIdentification) {
	/ / if there is no name specified is to use a unique identifier, and use DelegatingTransactionAttribute txAttr packing
	if(txAttr ! =null && txAttr.getName() == null) {
		txAttr = new DelegatingTransactionAttribute(txAttr) {
			@Override
			public String getName(a) {
				returnjoinpointIdentification; }}; } TransactionStatus status =null;
	if(txAttr ! =null) {
		if(tm ! =null) {
			/ / get TransactionStatusstatus = tm.getTransaction(txAttr); }}// Prepare a TransactionInfo based on the specified properties and status
	return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}
Copy the code

In creating a transaction method, you do three things:

  1. useDelegatingTransactionAttributepackagingtxAttrThe instance
  2. Get transaction:tm.getTransaction(txAttr)
  3. Building transaction information:prepareTransactionInfo(tm, txAttr, joinpointIdentification, status)

Core method in the second point and the third point, respectively, extract the core for familiarity.


Get TransactionStatus

status = tm.getTransaction(txAttr);

Since the code is longer, let me summarize some of the key points

The transaction

Create the corresponding transaction as an example, we use the DataSourceTransactionManager doGetTransaction method of creating based on JDBC transaction instance.

protected Object doGetTransaction(a) {
	DataSourceTransactionObject txObject = new DataSourceTransactionObject();
	txObject.setSavepointAllowed(isNestedTransactionAllowed());
	// Use the original link if the current thread already records the database link
	ConnectionHolder conHolder =
			(ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
	// false indicates that the connection is not newly created
	txObject.setConnectionHolder(conHolder, false);
	return txObject;
}
Copy the code

Where in the same thread, to determine whether there are duplicate transactions, is inTransactionSynchronizationManager.getResource(obtainDataSource())The key judgment logic is the following:

private static final ThreadLocal<Map<Object, Object>> resources =
			new NamedThreadLocal<>("Transactional resources");
			
private static Object doGetResource(Object actualKey) {
	Map<Object, Object> map = resources.get();
	if (map == null) {
		return null;
	}
	Object value = map.get(actualKey);
	// Transparently remove ResourceHolder that was marked as void...
	if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
		map.remove(actualKey);
		// Remove entire ThreadLocal if empty...
		if (map.isEmpty()) {
			resources.remove();
		}
		value = null;
	}
	return value;
}
Copy the code

Conclusion:resourcesIs aThreadLocalA thread is a private object, and each thread is stored independently. Therefore, whether a transaction exists depends on whether there are active transactions in the current thread and DataSourcemap.get(actualKey).


Process existing transactions

According to the previous statement, whether there is a transaction in the current thread is judged by the current thread’s record of the connection is not empty and in the connectionHoldertransactionActiveProperty is not null, and if a transaction exists in the current thread, it is processed according to different transaction propagation characteristics. The specific code logic is as follows:

if (isExistingTransaction(transaction)) {
	// Existing transaction found -> check propagation behavior to find out how to behave.
	// There is a transaction in the current thread
	return handleExistingTransaction(def, transaction, debugEnabled);
}
Copy the code

PROPAGATION_NEVER

Throw an exception, PROPAGATION_NEVER, which means that the method needs to run in a non-transactional environment, PROPAGATION_NEVER, and it throws an exception if a non-transactional method is called by an external transacted method:

if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
		throw new IllegalTransactionStateException(
				"Existing transaction found for transaction marked with propagation 'never'");
	}
Copy the code

PROPAGATION_NOT_SUPPORTED

If a transaction exists, suspend the transaction instead of throwing an exception:

if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
	Object suspendedResources = suspend(transaction);
	boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
	return prepareTransactionStatus(
			definition, null.false, newSynchronization, debugEnabled, suspendedResources);
}
Copy the code

Transaction pending

For pending operations, the main purpose is to record the state of the original transaction so that it can be recovered by subsequent operations:

In fact, suspend () method is invoked the transaction manager of DataSourceTransactionManager doSuspend () method

protected Object doSuspend(Object transaction) {
	DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
	// Set database connection to NULL
	txObject.setConnectionHolder(null);
	return TransactionSynchronizationManager.unbindResource(obtainDataSource());
}
Copy the code

Finally, the key method of call is TransactionSynchronizationManager# doUnbindResource

private static Object doUnbindResource(Object actualKey) {
	Map<Object, Object> map = resources.get();
	if (map == null) {
		return null;
	}
	Object value = map.remove(actualKey);
	if (map.isEmpty()) {
		resources.remove();
	}
	if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
		value = null;
	}
	if(value ! =null && logger.isTraceEnabled()) {
        Thread.currentThread().getName() + "]");
	}
	return value;
}
Copy the code

Read the article in The seventh Resources article and understand the operation of transaction suspension in combination with the code: the process of removing the current thread, the active transaction object of the data source

It is how to realize the transaction pending, the answer is in doSuspend () method of the txObject. SetConnectionHolder (null), will connectionHolder set to null.

aconnectionHolderRepresents a database connection object if it isnull, indicating that a connection must be fetched from the cache pool the next time it is needed. The automatic submission of a new connection istrue.


PROPAGATION_REQUIRES_NEW

Indicates that the current method must run in its own transaction, a new transaction will be started, and if one transaction is running, the method will be suspended while it is running.

SuspendedResourcesHolder suspendedResources = suspend(transaction);
try {
	booleannewSynchronization = (getTransactionSynchronization() ! = SYNCHRONIZATION_NEVER); DefaultTransactionStatus status = newTransactionStatus( definition, transaction,true, newSynchronization, debugEnabled, suspendedResources);
	// Create a new transaction
	doBegin(transaction, definition);
	prepareSynchronization(status, definition);
	return status;
}
catch (RuntimeException | Error beginEx) {
	resumeAfterBeginException(transaction, suspendedResources, beginEx);
	throw beginEx;
}
Copy the code

As with the previous method, the suspend method is also used under the PROPAGATION_REQUIRES_NEW broadcast property to suspend the original transaction. The doBegin() method is at the heart of the transaction initiation.


PROPAGATION_NESTED

Indicates that if a transaction is currently running, the method should run in a nested transaction. The nested transaction can be committed or rolled back independently of the encapsulated transaction. If the encapsulated transaction does not exist, the method behaves likePROPAGATION_REQUIRES_NEW.

For broker processing, there are two branches, similar to PROPAGATION_REQUIRES_NEW, which are not posted. Here’s how to use savePoint for transaction processing:

if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
	// Embedded transaction processing
	if (useSavepointForNestedTransaction()) {
		DefaultTransactionStatus status =
				prepareTransactionStatus(definition, transaction, false.false, debugEnabled, null);
		/ / create the savepoint
		status.createAndHoldSavepoint();
		returnstatus; }}Copy the code

For those of you who have studied databases, savePoint allows you to roll back part of a transaction using savepoints to make transaction processing more flexible and refined. Tracking code, found that create a save point is the method called org. HSQLDB. JDBC. JDBCConnection# setSavepoint (Java. Lang. String), interested can learn ~ down further


The transaction to create

In fact, this method appears in all of the previous methodsdoBegin()Create a transaction in this method, setting the isolation level of the database,timeoutProperties and SettingsconnectionHolder:

DataSourceTransactionManager#doBegin

protected void doBegin(Object transaction, TransactionDefinition definition) {
	DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
	Connection con = null;
	if(! txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) { Connection newCon = obtainDataSource().getConnection(); txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
	}

	txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
	con = txObject.getConnectionHolder().getConnection();
	// Set the isolation level
	Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
	txObject.setPreviousIsolationLevel(previousIsolationLevel);

	// configured the connection pool to set it already).
	// Change the automatic commit Settings, controlled by Spring
	if (con.getAutoCommit()) {
		txObject.setMustRestoreAutoCommit(true);
		con.setAutoCommit(false);
	}
	// Prepare the transaction connection
	prepareTransactionalConnection(con, definition);
	// Sets the criteria for determining whether a transaction exists on the current thread
	txObject.getConnectionHolder().setTransactionActive(true);

	int timeout = determineTimeout(definition);
	if(timeout ! = TransactionDefinition.TIMEOUT_DEFAULT) { txObject.getConnectionHolder().setTimeoutInSeconds(timeout); }// Bind the connection holder to the thread.
	if (txObject.isNewConnectionHolder()) {
		// Bind the currently acquired connection to the current threadTransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder()); }}}Copy the code

Conclusion: Spring transactions are started by setting the database commit property to false


summary

In declarative transaction processing, there are mainly the following steps:

  1. Gets the properties of the transaction:tas.getTransactionAttribute(method, targetClass)
  2. Load the configured in the configurationTransactionManager:determineTransactionManager(txAttr);
  3. Different transaction processing methods use different logic: for more information about declarative and programmatic transactions, see the Spring Programmatic and Declarative Transaction Example tutorial
  4. Gets the transaction and collects the transaction letter before the target method executesInterest rates:createTransactionIfNecessary(tm, txAttr, joinpointIdentification)
  5. Implement target method:invocation.proceed()
  6. An exception occurs. Try exception handling:completeTransactionAfterThrowing(txInfo, ex);
  7. The transaction information is eliminated before committing the transaction:cleanupTransactionInfo(txInfo)
  8. Commit the transaction:commitTransactionAfterReturning(txInfo)

Transaction rollback & Commit

These two steps, which mainly call the API of the underlying database connection, are not detailed.


conclusion

This article briefly documented how to use Spring transactions and how to implement them in code.

In previous usage scenarios, only declarative transactions configured by default were used@Transactional, do not understand the meaning of other attribute Settings, and do not know that transactions are not supported if methods in the same class call themselves under the default configuration.

So, after learning and summarizing this time, the next time you use it, you can know what problems can be solved by different property Settings, such as modifying broadcast featuresPROPAGATION, let the transaction support method invocation, and set the transaction timeouttimeout, isolation level, etc.


Due to limited personal skills, if there is any misunderstanding or mistake, please leave a comment, and I will correct it according to my friends’ suggestions

Gitee address https://gitee.com/vip-augus/spring-analysis-note.git

Making the address https://github.com/Vip-Augus/spring-analysis-note


The resources

  1. Understand the use of @Transactional in Spring
  2. Spring – @ Transactional annotation
  3. Two transaction configurations commonly used in Spring, as well as transaction propagation and isolation levels
  4. Spring transaction pointcut analysis details
  5. Advisor, Advice, Pointcut in Spring
  6. Spring programming and declarative transaction example tutorials
  7. spring-transaction
  8. The savepoint principle
  9. — Beijing: Posts and Telecommunications Press

Portal:

  • Spring source learning – environment preparation

  • (1) The infrastructure of the container

  • Spring source code learning (2) default tag parsing

  • Spring source code learning (3) custom tags

  • Spring source code learning (four) bean loading

  • Spring source code learning (5) loop dependency

  • Spring source code learning (six) extension function part 1

  • Spring source code learning (seven) extension features part 2

  • Spring source learning (eight) AOP use and implementation principle

  • Spring source code learning (9) Transaction Transaction

  • (10) Spring MVC