SpringBoot AOP configuring global transactions

Posted by Chris-the dude on Sat, 29 Jan 2022 18:12:15 +0100

The emergence of SpringBoot makes it very easy to use transactions in projects. There are two ways to use them, which are suitable for annotation transactions (declarative transaction management) of small projects and global transactions of large projects.

1. Annotation transactions. (secondary)

There are only two steps to annotate transactions. Turn on the transaction annotation function and use the transaction annotation function, and only one annotation is used in each step.

Step 1: enable transaction annotation function @ EnableTransactionManagement

Add the annotation @ EnableTransactionManagement in the main startup class.

package com.gx;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@EnableTransactionManagement //Enable transaction annotation
@SpringBootApplication
public class Ch09SpringbootTransAnnoApplication {

    public static void main(String[] args) {
        SpringApplication.run(Ch09SpringbootTransAnnoApplication.class, args);
    }

}
Step 2: use the transaction annotation function @ Transactional

Add @ Transactional to the service interface implementation class or interface implementation class method.

package com.gx.service.impl;

import com.gx.service.StudentService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.gx.domain.Student;

@Service
public class StudentServiceImpl implements StudentService {

    @Transactional //Use annotation transactions
    @Override
    public String addStudent(Student student) {
        //Business method
    }
}

Note: @ Transactional must be added to the public modified method.

2. Global transactions. (main)

SpringBoot global transactions mainly use AOP aspect programming.

Step 1: create the facet class @ Aspect.

Create an ordinary class. After adding @ Aspect, this class is a faceted class, which is used to write transaction functions. At the same time, you also need to define the section class as a Configuration class and add the annotation @ Configuration.

Notes: 1, @ Aspect defines this class as a facet class, and the current class is read by the container as a facet.

2. @ Configuration defines this class as a Configuration class, configures the spring container, and injects bean s

package com.gx.config;

import org.aspectj.lang.annotation.Aspect;
import org.springframework.context.annotation.Configuration;

@Aspect //Define the facet class and identify the current class as a facet for the container to read
@Configuration //Define configuration class
public class TransactionAdviceConfig {
	//Enhancement method
}
Step 2: create the first method, return the transaction interceptor, declare the transaction attribute of the business method, and register it in the bean.
  • If you need to return the transaction interceptor, you need a new transaction interceptor. According to the class of TransactionInterceptor, there are only two methods to create a TransactionInterceptor.
    public TransactionInterceptor() {
        
    }
    
    public TransactionInterceptor(TransactionManager ptm, TransactionAttributeSource tas) {
        this.setTransactionManager(ptm);
        this.setTransactionAttributeSource(tas);
    }
    

    To use transactions, there must be transaction manager and transaction attribute transaction attributesource. When configuring the transaction attribute, it is generally filtered by the name of the method, such as add *, save *, delete *, etc., so the transaction attribute uses its subclass NameMatchTransactionAttributeSource.

    //Transaction manager
    @Autowired
    private TransactionManager transactionManager;
    
    @Bean
    public TransactionInterceptor txAdvice() {
        //Declare an object that configures transaction properties by method name
    	NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
    	//Returns a transaction interceptor
        return new TransactionInterceptor(transactionManager, source);
    }
    
  • Set the transaction attribute of business method by method name.

    There are only two methods we use frequently in the NameMatchTransactionAttributeSource class. setNameMap is actually a collection of addtransactionalmethods.

    //Set transaction attributes for multiple methods or multi class methods through the map set
    public void setNameMap(Map<String, TransactionAttribute> nameMap) {
        nameMap.forEach(this::addTransactionalMethod);
    }
    //Set the transaction attribute of a method or a class of methods through the method name or a class of method name and transaction attribute
    public void addTransactionalMethod(String methodName, TransactionAttribute attr) {
        if (logger.isDebugEnabled()) {
            logger.debug("Adding transactional method [" + methodName + "] with attribute [" + attr + "]");
        }
        if (this.embeddedValueResolver != null && attr instanceof DefaultTransactionAttribute) {
            ((DefaultTransactionAttribute) attr).resolveAttributeStrings(this.embeddedValueResolver);
        }
        this.nameMap.put(methodName, attr);
    }
    
  • Set transaction properties.

    TransactionAttribute: transaction attribute. There are many implementation classes. Generally, the rule-based transaction attribute RuleBasedTransactionAttribute is used. Most of the functions are in its parent class DefaultTransactionDefinition. Only a small part can be written, and other transaction attributes can be written according to business requirements.

    //Configure a transaction attribute (read-only)
    RuleBasedTransactionAttribute readOnlyTx = new RuleBasedTransactionAttribute();
    //Read only
    readOnlyTx.setReadOnly(true);
    //Transaction propagation behavior
    readOnlyTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
    
    //Set business method transaction attributes by method name
    Map<String, TransactionAttribute> txMap = new HashMap<>();
    txMap.put("get*", readOnlyTx);
    

    Then add it to NameMatchTransactionAttributeSource

    source.setNameMap(txMap);
    

    Method of setting transaction properties

    1,Transaction propagation behavior
    setPropagationBehavior();
    2,Transaction isolation level
    setIsolationLevel();
    3,Transaction timeout
    setTimeout();
    4,Transaction read only
    setReadOnly();
    5,Set transaction name
    setName();
    6,Set rollback rules
    setRollbackRules();
    
  • Transaction properties. (Extended)

    Transaction propagation behavior

    Transaction behaviorexplain
    PROPAGATION_REQUIREDCurrent transaction is supported. It is assumed that there is no current transaction. Just create a new transaction
    PROPAGATION_SUPPORTSSupports the current transaction. If there is no transaction at present, it will run in a non transaction mode
    PROPAGATION_MANDATORYSupports the current transaction. If there is no transaction, an exception will be thrown
    PROPAGATION_REQUIRES_NEWCreate a new transaction, assuming that there is a transaction currently. Suspend the current transaction
    PROPAGATION_NOT_SUPPORTEDRun the operation in a non transactional manner. If there is a current transaction, suspend the current transaction
    PROPAGATION_NEVERRun in non transactional mode. If there is a transaction currently, an exception will be thrown
    PROPAGATION_NESTEDIf a transaction currently exists, it is executed within a nested transaction. If there is no current transaction, execute the same as the deployment_ Required similar operations.

    There are only four transaction isolation levels, but spring provides five. (the isolation level name of spring is different from that in the database)

    Isolation levelexplainDirty readingUnreal readingNon repeatable reading
    ISOLATION_DEFAULTThe default isolation level. The transaction isolation level supported by each database is different, which changes according to the database used.---
    ISOLATION_READ_UNCOMMITTEDRead uncommitted, that is, data that has not been submitted can be read.yesyesyes
    ISOLATION_READ_COMMITTEDRead submitted, that is, you can read the submitted data.noyesyes
    ISOLATION_REPEATABLE_READRepeat reading, that is, lock after the data is read out. If this transaction does not end, other transactions cannot operate this data.nonoyes
    ISOLATION_SERIALIZABLESerialization is the highest transaction isolation level. No matter how many transactions, all sub transactions of one transaction can be executed after running all sub transactions of another transaction one by one.nonono
Step 3: configure the Advisor to enhance the transaction.
  • Create an adapter (a normal method returns to Advisor).

    Advisor is composed of pointcut and Advice, but advisor is an interface. It needs to implement the class DefaultPointcutAdvisor and pass in parameter pointcut and Adivce.

    @Bean
    public Advisor txAdviceAdvisor() {
        //Enhance transactions to associate pointcuts and transaction attributes
        return new DefaultPointcutAdvisor(breakthrough point, Advice);
    }
    
  • Configure pointcuts.

    //Configure pointcut expression: specify which classes in the package use transactions and set them as static class constants
    private static final String AOP_POINTCUT_EXPRESSION = "execution (* com.***.service.*.*(..))";
    
    //Put the following contents in the adapter method
    //Configure transaction pointcut expression
    AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
    pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
    
  • Associate pointcuts and Advice.

    //Enhance transactions to associate pointcuts and transaction attributes
    return new DefaultPointcutAdvisor(pointcut, txAdvice());
    
Step 4: restart the test.

Finally, I present all the codes of aop global transaction

package com.gx.config;

import org.aspectj.lang.annotation.Aspect;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.interceptor.*;

import javax.sql.DataSource;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

@Aspect //Define the facet class and identify the current class as a facet for the container to read
@Configuration //Define configuration class
public class TransactionAdviceConfig {

    //The transaction timeout is 10 seconds
    private static final int TX_METHOD_TIMEOUT = 10;

    //Configure pointcut expressions: specify which classes in the package use transactions
    private static final String AOP_POINTCUT_EXPRESSION = "execution (* com.***.service.*.*(..))";

    //Transaction manager
    @Autowired
    private TransactionManager transactionManager;
    /**
     * Declare transaction properties of business methods
     */
    @Bean
    public TransactionInterceptor txAdvice() {

        /**
         * Read only transactions are configured here
         */
        RuleBasedTransactionAttribute readOnlyTx = new RuleBasedTransactionAttribute();
        readOnlyTx.setReadOnly(true);//Read only
        readOnlyTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);//Propagation behavior of transactions

        /**
         * Transaction is required
         * If there is a current transaction, use the current transaction. If there is no current transaction, start a new transaction
         */
        RuleBasedTransactionAttribute requiredTx = new RuleBasedTransactionAttribute();
        //Check exceptions are also rolled back
        requiredTx.setRollbackRules(
                Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
        requiredTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        requiredTx.setTimeout(TX_METHOD_TIMEOUT);

        /**
         * Execute without transactions and suspend any existing transactions
         */
        RuleBasedTransactionAttribute noTx = new RuleBasedTransactionAttribute();
        noTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);

        /**
         * Transaction corresponding to setting method
         */
        Map<String, TransactionAttribute> txMap = new HashMap<>();
        //Read only transaction
        txMap.put("get*", readOnlyTx);
        txMap.put("query*", readOnlyTx);
        txMap.put("find*", readOnlyTx);
        txMap.put("list*", readOnlyTx);
        txMap.put("count*", readOnlyTx);
        txMap.put("exist*", readOnlyTx);
        txMap.put("search*", readOnlyTx);
        txMap.put("fetch*", readOnlyTx);
        //No transaction
        txMap.put("noTx*", noTx);
        //Write transaction
        txMap.put("add*", requiredTx);
        txMap.put("save*", requiredTx);
        txMap.put("insert*", requiredTx);
        txMap.put("update*", requiredTx);
        txMap.put("modify*", requiredTx);
        txMap.put("delete*", requiredTx);
		
        NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
        source.setNameMap(txMap);
        
		//Return transaction interceptor
        return new TransactionInterceptor(transactionManager, source);
    }

    @Bean
    public Advisor txAdviceAdvisor() {
        //Configure transaction pointcut expression
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
        //Enhance transactions to associate pointcuts and transaction attributes
        return new DefaultPointcutAdvisor(pointcut, txAdvice());
    }
}

Topics: MySQL Spring AOP Transaction