preface
Today we’ll talk about Spring’s declarative transactions.
start
Speaking of declarative transactions, let’s review the concept of a transaction. What is a transaction? A transaction is a logical set of operations, and the units that make up that set of operations either all succeed or all fail. Thus ensuring the accuracy and security of data. Transactions have four properties (ACID), respectively
Atomicity refers to the fact that a transaction is an indivisible unit of work in which all or none of the operations occur.
A Consistency transaction must change a database from one consistent state to another.
Isolation The Isolation of a transaction refers to the transaction initiated by the database for each user when multiple users concurrently access the database. Each transaction cannot be disturbed by the operation data of other transactions, and multiple concurrent transactions must be isolated from each other.
Durability Durability means that once a transaction is committed, its changes to data in the database are permanent and then even if the database fails
Nor should it affect it in any way.
There are two ways to implement transaction control in Spring: programmatic and declarative transactions. Programmatic transaction refers to the addition of transaction control code in the code, and declarative transaction refers to the use of XML or annotations to configure the control transaction, the following takes pure annotations configuration declarative transaction as an example to analyze.
Spring open declarative transaction annotations are @ EnableTransactionManagement, said the first thing to understand some here, spring’s transaction manager management actually is the way of using aop, by creating a dynamic proxy and interception, implementation of the transaction management. This annotation is added to spring’s configuration class to support declarative transactions, so how spring can support transactions through such an annotation, let’s look at the code.
So the first thing we see is that on this annotation, we import a selector
@Import(TransactionManagementConfigurationSelector.class)
Copy the code
So let’s look at this piece of code in this Selector class
@Override protected String[] selectImports(AdviceMode adviceMode) { switch (adviceMode) { case PROXY: return new String[] {AutoProxyRegistrar.class.getName(), ProxyTransactionManagementConfiguration.class.getName()}; case ASPECTJ: return new String[] {determineTransactionAspectClass()}; default: return null; }}Copy the code
This code, the introduction of AutoProxyRegistrar and ProxyTransactionManagementConfiguration these two classes, we first see AutoProxyRegistrar this class, this class has a code
If (mode = = AdviceMode. PROXY) {/ / the important thing is that the code AopConfigUtils. RegisterAutoProxyCreatorIfNecessary (registry); if ((Boolean) proxyTargetClass) { AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); return; }} / / we are into this method @ Nullable public static BeanDefinition registerAutoProxyCreatorIfNecessary (BeanDefinitionRegistry Registry, @ Nullable Object source) {/ / can see introduced InfrastructureAdvisorAutoProxyCreator this class, So what is this class return registerOrEscalateApcAsRequired (InfrastructureAdvisorAutoProxyCreator. Class, registry, source); } / / have a look at the first public class InfrastructureAdvisorAutoProxyCreator extends AbstractAdvisorAutoProxyCreator {@ Nullable private ConfigurableListableBeanFactory beanFactory; @Override protected void initBeanFactory(ConfigurableListableBeanFactory beanFactory) { super.initBeanFactory(beanFactory); this.beanFactory = beanFactory; } @Override protected boolean isEligibleAdvisorBean(String beanName) { return (this.beanFactory ! = null && this.beanFactory.containsBeanDefinition(beanName) && this.beanFactory.getBeanDefinition(beanName).getRole() == BeanDefinition.ROLE_INFRASTRUCTURE); }}Copy the code
Take a look at the inheritance diagram
Can see the method of indirect inheritance in SmartInstantiationAwareBeanPostProcessor, eventually inherit from BeanPostProcessor, This shows InfrastructureAdvisorAutoProxyCreator class is a rear processor, And with the spring AOP open @ EnableAspectJAutoProxy registered AnnotationAwareAspectJProxyCreator to implement the same interface, This corresponds to what I said earlier about declarative transactions being an application of springAOP ideas.
And then we come back to see ProxyTransactionManagementConfiguration this class, we saw one transaction enhancer, parser and an attribute is a transaction interceptor
@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor( TransactionAttributeSource transactionAttributeSource, TransactionInterceptor TransactionInterceptor) {/ / transaction intensifier BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor(); / / injection properties parser advisor. SetTransactionAttributeSource (transactionAttributeSource); // inject the transactionInterceptor advisor.setadvice (transactionInterceptor); if (this.enableTx ! = null) { advisor.setOrder(this.enableTx.<Integer>getNumber("order")); } return advisor; } @ Bean @ Role (BeanDefinition ROLE_INFRASTRUCTURE) / / public attribute the parser TransactionAttributeSource transactionAttributeSource() { return new AnnotationTransactionAttributeSource(); } @bean@role (BeanDefinition.ROLE_INFRASTRUCTURE) public TransactionInterceptor transactionInterceptor(TransactionAttributeSource transactionAttributeSource) { TransactionInterceptor interceptor = new TransactionInterceptor(); interceptor.setTransactionAttributeSource(transactionAttributeSource); if (this.txManager ! = null) { interceptor.setTransactionManager(this.txManager); } return interceptor; }Copy the code
Take a look at the property resolver
/ / comment private final parser Set Set < TransactionAnnotationParser > annotationParsers;Copy the code
This is a collection of annotation parsers to which you can add a variety of annotation parsers. In this case, we will focus on the Spring transaction annotation parser, SpringTransactionParser
protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) { RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute(); 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
You can see that the Enum and ClassArray in this code are actually related attributes in the @Transaction annotation. One of the purposes of this attribute parser is to parse the attributes in the @Transaction annotation
With the property parser out of the way, let’s move on to the TransactionInterceptor, where this code is important
@Override @Nullable public Object invoke(MethodInvocation invocation) throws Throwable { // Work out the target class: may be {@code null}. // The TransactionAttributeSource should be passed the target class // as well as the method, which may be from an interface. Class<? > targetClass = (invocation.getThis() ! = null ? AopUtils.getTargetClass(invocation.getThis()) : null); // Adapt to TransactionAspectSupport's invokeWithinTransaction... // Add transaction support for return invokeWithinTransaction(Invocation. GetMethod (), targetClass, Invocation ::proceed); }Copy the code
And then we go into this method
@Nullable protected Object invokeWithinTransaction(Method method, @Nullable Class<? > targetClass, final InvocationCallback invocation) throws Throwable { // If the transaction attribute is null, The method is non-transactional. // Get the attribute parser, In the configuration class ProxyTransactionManagementConfiguration configuration with TransactionAttributeSource tas = getTransactionAttributeSource (); final TransactionAttribute txAttr = (tas ! = null ? tas.getTransactionAttribute(method, targetClass) : null); final TransactionManager tm = determineTransactionManager(txAttr); if (this.reactiveAdapterRegistry ! = null && tm instanceof ReactiveTransactionManager) { ReactiveTransactionSupport txSupport = this.transactionSupportCache.computeIfAbsent(method, key -> { if (KotlinDetector.isKotlinType(method.getDeclaringClass()) && KotlinDelegate.isSuspend(method)) { throw new TransactionUsageException( "Unsupported annotated transaction on suspending function detected: " + method + ". Use TransactionalOperator.transactional extensions instead."); } ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(method.getReturnType()); if (adapter == null) { throw new IllegalStateException("Cannot apply reactive transaction to non-reactive return type: " + method.getReturnType()); } return new ReactiveTransactionSupport(adapter); }); return txSupport.invokeWithinTransaction( method, targetClass, invocation, txAttr, (ReactiveTransactionManager) tm); } / / get the transaction manager PlatformTransactionManager PTM = asPlatformTransactionManager (tm); final String joinpointIdentification = methodIdentification(method, targetClass, txAttr); if (txAttr == null || ! (ptm instanceof CallbackPreferringPlatformTransactionManager)) { // Standard transaction demarcation with getTransaction and commit/rollback calls. TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification); Object retVal; try { // This is an around advice: Invoke the next interceptor in the chain. // This will normally result in a target object being invoked. retVal = invocation.proceedWithInvocation(); // Target Invocation exception // Target invocation exception, Will a rollback operation completeTransactionAfterThrowing (txInfo, ex); throw ex; } finally { cleanupTransactionInfo(txInfo); } if (vavrPresent && VavrDelegate.isVavrTry(retVal)) { // Set rollback-only in case of Vavr failure matching our rollback rules... TransactionStatus status = txInfo.getTransactionStatus(); if (status ! = null && txAttr ! = null) { retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status); }} / / target method run normally, can perform commitTransactionAfterReturning, perform transactions commit operation commitTransactionAfterReturning (txInfo); return retVal; } else { final ThrowableHolder throwableHolder = new ThrowableHolder(); // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in. try { Object result = ((CallbackPreferringPlatformTransactionManager) ptm).execute(txAttr, status -> { TransactionInfo txInfo = prepareTransactionInfo(ptm, txAttr, joinpointIdentification, status); try { Object retVal = invocation.proceedWithInvocation(); if (vavrPresent && VavrDelegate.isVavrTry(retVal)) { // Set rollback-only in case of Vavr failure matching our rollback rules... retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status); } return retVal; } catch (Throwable ex) { if (txAttr.rollbackOn(ex)) { // A RuntimeException: will lead to a rollback. if (ex instanceof RuntimeException) { throw (RuntimeException) ex; } else { throw new ThrowableHolderException(ex); } } else { // A normal return value: will lead to a commit. throwableHolder.throwable = ex; return null; } } finally { cleanupTransactionInfo(txInfo); }}); // Check result state: It might indicate a Throwable to rethrow. if (throwableHolder.throwable ! = null) { throw throwableHolder.throwable; } return result; } catch (ThrowableHolderException ex) { throw ex.getCause(); } catch (TransactionSystemException ex2) { if (throwableHolder.throwable ! = null) { logger.error("Application exception overridden by commit exception", throwableHolder.throwable); ex2.initApplicationException(throwableHolder.throwable); } throw ex2; } catch (Throwable ex2) { if (throwableHolder.throwable ! = null) { logger.error("Application exception overridden by commit exception", throwableHolder.throwable); } throw ex2; }}}Copy the code
conclusion
In general, this is how Spring implements declarative transactions
-
@ EnableTransactionManagement annotations, by introducing the @ import TransactionManagementConfigurationSelector class, its selectImports method to import the other two categories: AutoProxyRegistrar and ProxyTransactionManagementConfiguration
-
RegisterBeanDefinitions is the AutoProxyRegistrar method InfrastructureAdvisorAutoProxyCreator, introduced by AopConfigUtils. RegisterAutoProxyCreatorIfNecessary (registry), is a rear handler class
-
ProxyTransactionManagementConfiguration is an added @ Configuration annotation Configuration class, Register the transaction enhancer (injection properties parser, transaction interceptor) AnnotationTransactionAttributeSource and TransactionInterceptor, AnnotationTransactionAttributeSource internal hold a parser collection Set annotationParsers, specific use SpringTransactionAnnotationParser parser, The Transactional TransactionInterceptor implements the MethodInterceptor interface. This generic interception is merged with AOP enhancements to affect proxy objects before they are generated. The invokeWithinTransaction method in the TransactionInterceptor invokes the original business logic invocation (enhanced transaction)