Handwritten Spring Transactions

Spring transactions are divided into declarative transactions (annotation or package scan) and programmatic transactions (commit or roll back in code). Declarative transactions are wrapped on the basis of programmatic transactions with AOP counts. Database connection pool configuration and RootConfig I imported the following Maven dependency

<dependencies> <! <dependency> <groupId>org.springframework</groupId> <artifactId> Spring-test </artifactId> < version > 4.3.20. RELEASE < / version > < / dependency > < the dependency > < groupId > org. Springframework < / groupId > < artifactId > spring - the core < / artifactId > < version > 4.3.20. RELEASE < / version > < / dependency > < the dependency > < the groupId > org. Springframework < / groupId > < artifactId > spring - the context < / artifactId > < version > 4.3.20. RELEASE < / version > </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> < version > 4.3.20. RELEASE < / version > < / dependency > < the dependency > < groupId > org. Springframework < / groupId > < artifactId > spring - the orm < / artifactId > < version > 4.3.20. RELEASE < / version > < / dependency > < the dependency > <groupId>org.aspectj</groupId> <artifactId> aspectJrt </artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId> aspectJ </groupId> <artifactId> AspectJweaver </artifactId> <version>1.5.3</version> </dependency> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.1_2</version> </dependency> <! --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> The < version > 8.0.13 < / version > < / dependency > <! Alibaba </groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency> <! GroupId > <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies>Copy the code

The configuration class follows, which replaces the somewhat outdated XML configuration Spring

@Configuration
@ComponentScan(basePackages = {"com.libi"})
@EnableAspectJAutoProxy
public class RootConfig {
    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl("jdbc:mysql://localhost:3306/sms?userSSL=true&useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

    @Bean
    public DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource) {
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }
}
Copy the code

The method to join a transaction is as follows: The userDao manipulates data and throws exceptions at intervals

 @Service
 public class UserServiceImpl implements UserService {
     @Autowired
     private UserDao userDao;
     public void add() {
         userDao.add("test001"."1233321");
         System.out.println("The interval in between, and there is an anomaly.");
         int i = 1 / 0;
         userDao.add("test002"."135365987"); }}Copy the code

Only the test001 statement will be inserted, but test002 will not be successfully inserted.

Programmatic transaction

At this point we encapsulate a transaction tool

@Component
@Scope("prototype") public class TransactionUtils { @Autowired private DataSourceTransactionManager dataSourceTransactionManager; private TransactionStatus status; /** Start transaction */ public TransactionStatusbeginThe spread of () {/ / use the default level TransactionStatus transaction = dataSourceTransactionManager. GetTransaction (new DefaultTransactionAttribute());returntransaction; } /** The transaction state is passed in */ public voidcommit() { dataSourceTransactionManager.commit(status); } /** Rollback of the transaction requires passing in the transaction state */ public voidrollBack() {// Get the current transaction and roll back if there is oneif(status ! = null) { dataSourceTransactionManager.rollback(status); }}}Copy the code

Using it again, modify the add method

    public void add() {
        TransactionStatus begin = null;
        try {
            begin = transactionUtils.begin();
            userDao.add("test001"."1233321");
            System.out.println("The interval in between, and there is an anomaly.");
            int i = 1 / 0;
            userDao.add("test002"."135365987"); transactionUtils.commit(); } catch (Exception e) { e.printStackTrace(); transactionUtils.rollBack(); }}Copy the code

Declarative transaction

We use AOP programming to wrap the transaction tools we just described

@Component @Aspect public class AopTransaction { @Autowired private TransactionUtils transactionUtils; @Around("execution(* com.libi.service.UserService.add(..) )") public void around(ProceedingJoinPoint ProceedingJoinPoint) throws Throwable {system.out.println (" Start transaction "); proceedingJoinPoint.proceed(); System.out.println(" Commit transaction "); transactionUtils.commit(); } @AfterThrowing("execution(* com.libi.service.UserService.add(..) )") public void afterThrowing() {system.out.println ("); / / get the current transaction, directly rollback TransactionAspectSupport. CurrentTransactionStatus (). The setRollbackOnly (); }}Copy the code

Then empty all the try code in the original method and return it to its original state (cannot catch exceptions, or cannot be caught by exception notification, resulting in invalid transaction)

Annotated transaction

Spring has helped us implement class annotation transactions, so we need to add the following annotations to the configuration class to enable annotation transactions support

@EnableTransactionManagement
Copy the code

Then comment out our last AOP annotation and open the Transactional method using the @Transactional(rollbackFor = exception.class) annotation. RollbackFor identifies the Exception class that needs to be rolled back. The whole method looks like this

@Transactional(rollbackFor = Exception.class) public void add() { userDao.add("test001", "1233321"); System.out.println(" middle interval, and exception "); int i = 1 / 0; userDao.add("test002", "135365987"); }Copy the code

This also implements transactions for this method. Of course, there is no way to catch exceptions in this method, which will still result in the transaction not being able to trigger the exception notification and invalidate the transaction. We use this effect as a framework for template handwritten transactions


Specific steps

  • Custom annotation
@target ({ElementType.METHOD}) @Retention(retentionPolicy.runtime) public @interface ExtTransaction { }Copy the code
  • Encapsulate manual transactions (using the original TransactionUtils class)
  • Use AOP to scan annotations under the specification package
    • Encapsulate the action of finding and annotating annotations on AOP
@Component @Aspect public class AopAnnotationTransaction { @Autowired private TransactionUtils transactionUtils; /** Around("execution(* com.libi.service.*.*(..)); / / Around("execution(* com.libi.service. ExtTransaction ExtTransaction = getExtTransaction(proceedingJoinPoint); TransactionStatus status = null; if (extTransaction ! = null) {system.out.println (" start transaction "); status = transactionUtils.begin(); } / / target method call agent proceedingJoinPoint. Proceed (); if (status ! // Commit transaction system.out.println (" Commit transaction "); transactionUtils.commit(); }} / abnormal notice * * * affairs / @ AfterThrowing (" execution (* com. Libi. Service. *. *. * (..) )") public void afterThrowing() {system.out.println ("); transactionUtils.rollBack(); */ Private ExtTransaction getExtTransaction(ProceedingJoinPoint ProceedingJoinPoint) throws NoSuchMethodException {/ / get a proxy object method String methodName = proceedingJoinPoint. GetSignature (). The getName (); Class<? > targetClass = proceedingJoinPoint.getTarget().getClass(); Class[] parameterTypes = ((MethodSignature) (proceedingJoinPoint.getSignature())).getParameterTypes(); Method targetMethod = targetClass.getMethod(methodName, parameterTypes); / / get in the way that annotations return targetMethod. GetAnnotation (ExtTransaction. Class); }}Copy the code

Also note that the TransactionUtils class still needs multiple instances, otherwise thread-safety issues will occur

Transaction propagation behavior

  • Propagation: The Propagation behaviors of transactions are generated in the calling transactions. In other words, when small transactions are nested in large transactions, what behaviors are generated
  • Types of transmission behavior
    • PROPAGATION_REQUIRED – Use the current transaction if there is one, or create a new one if there is none. This is the most common choice. (If the large method has a transaction, then the small method that requires the transaction is added to the transaction, if the large method has no transaction, then the transaction is created.)
    • PROPAGATION_SUPPORTS– Supports the current transaction, and executes non-transactionally if there is no transaction currently. // (If the outer method has no transaction, it executes as non-transactional. This is equivalent to default no transaction.
    • PROPAGATION_MANDATORY– The current transaction is supported, and an exception is thrown if there is no transaction currently.
    • PROPAGATION_REQUIRES_NEW– Create a transaction, and suspend the current one if it exists (no impact, suspend the big one when running a small one).
    • PROPAGATION_NOT_SUPPORTED– Executes an operation in a non-transactional manner, and suspends the current transaction if one exists.
    • — If there is a transaction, it is executed in a non-transaction manner
    • PROPAGATION_NEVER– Executes non-transactionally, throws an exception if a transaction currently exists.