Handwriting Spring transaction operations using AOP

Keywords: Java Spring Spring Boot

Preface

AOP is a special feature of Spring. Face-oriented programming provides developers with a new way of development. It does not intrude into business logic, do not modify the code of business logic, and realize some necessary auxiliary functions of programs, such as output log, permission checking, transaction processing, etc. Elegant AOP makes the methods of programs no longer tightly coupled to achieve the purpose of decoupling.Use as you want, not as you don't want.

Introduction to AOP

AOP is also face-to-face programming, Aspect Oriented Programming. The idea of AOP is to extract unnecessary functions into a face class, implement various enhancements in the face class, then proxy the previously implemented business class through dynamic proxy, and invoke the enhancements we specified at the specified time of the original business method.
As a saying goes, one method is not fully functional. I want to do A before the method starts, B after the method returns, C when the method throws an exception. If I write all the methods into the original business methods, the business methods will be bloated and difficult to maintain. If these A, B, C things are to be done in each business class,Then I would like to add A, B, C to every business class, so the tedious work is undoubtedly maddening.
So we take out methods A, B, C and write them in a facet class, and specify when and which methods of which classes these A, B, C methods should be called. This decouples business methods and enhancements. We just need to maintain the methods in the facet class, modify their invocation times, and we can achieve all the goals by doing little.

AOP Terms

Tangent: A method that needs to be enhanced
Cross-sectional focus: refers to the four invocation timing of a method, before start, after return, after exception, after
Notice: Additional ways to add
Face: Tangent + Notification
Four cross-cutting concerns:

  1. Front, at the beginning of the method
  2. Post, the method is executed eventually, whether or not it is an exception, equivalent to a finally block
  3. Exceptions, when a method throws an exception, are equivalent to catch
  4. Return, the normal execution of the method is complete, equivalent to before return

How transactions are implemented

In JAVA, we use DataSource to getConnection. The connection we take out is autoCommit, which means that every time we use Connection to operate the database, we automatically start a transaction for us, and every time we commit.
We know that the atomicity of a transaction requires that the operations in the transaction be executed either all at once or none at a time. If the methods in the Service class are executed once according to the automatic commit of the initial connection, the preceding operations may succeed and the subsequent operations fail, causing the atomicity of the transaction to be destroyed.
In order to prevent things that destroy atomicity, we need to make sure that transactions are committed only when we need them, and that they are rolled back when an exception occurs, not every time, so we need to make sure the following:

  1. Transaction cannot be committed automatically
  2. All operations use the same connection
  3. Rollback is required when throwing an exception
  4. Submit it in time for normal operation

Imagine a transaction as a block of code:

void transaction(){
	// Before advice
	Connection conn = getConnection();
	conn.setAutoCommit(false);
	try{
		Object val = method.invoke();
		// Return Notification
		conn.commit();
		return val;
	}catch(Exception e){
	// Exception Notification
		conn.rollback();
	}finally{
	// Final Notification
		conn.setAutoCommit(true);
		conn.close();
	}
	
	
}

How to implement transactions with AOP

The idea for AOP to implement transactions is really simple:

  1. When the method starts calling, get the connection and set the automatic commit of the connection to false
  2. AOP has to help us commit the transaction when the method is properly executed
  3. AOP has to help us roll back the transaction in case of method exception
  4. Whether an exception occurs or not, you need to put the connection back into the connection pool and restore the autocommit back to true

Specific implementation ideas

Once you have the idea, it is clear that according to the corresponding requirements, it can be converted into the implementation of technology:

  1. At the beginning of all Service methods, a pre-notification is made
  2. Make an AfterReturning notification to help us submit when the method is finished
  3. At the end of the method, help us close the connection and make an After notification
  4. When a method throws an exception, do an AfterThrowing notification to help us roll back

Specific code implementation

Project structure


There are only three core classes: UserService, UserMapper, AopTransaction

AOP implementation

package com.csw.aoptransaction.aop;


import com.csw.aoptransaction.util.ConnectionUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.sql.Connection;
import java.sql.SQLException;

@Aspect
@Component
@Slf4j
public class AopTransaction{
    @Pointcut("execution(public * *..*Service.*(..))")
    public void serviceCut(){}

    @Before(value = "serviceCut()")
    public void transactionStart() throws SQLException {
        log.info("===========Transaction Start=========");
        Connection connection = ConnectionUtil.getConnection(Thread.currentThread());
        connection.setAutoCommit(false);
    }
    @After(value = "serviceCut()")
    public void transactionAfter() throws SQLException {
        Connection connection = ConnectionUtil.getConnection(Thread.currentThread());
        connection.setAutoCommit(true);
        connection.close();
        log.info("=====Transaction ended, connection closed======");
    }
    @AfterReturning(value = "serviceCut()")
    public void transactionAfterReturning() throws SQLException {
        Connection connection = ConnectionUtil.getConnection(Thread.currentThread());
        connection.commit();
        log.info("===========Submit===========");
    }
    @AfterThrowing(value = "serviceCut()",throwing = "e")
    public void transactionAfterThrowing(Exception e) throws SQLException {
        Connection connection = ConnectionUtil.getConnection(Thread.currentThread());
        connection.rollback();
        log.info("Error encountered:"+e.getMessage());
        log.info("===========RollBACK===========");
    }
}


To ensure that each thread gets the same connection, I refer to the implementation of ThreadLocal, where each thread gets its own connection, stores a Connection in the Map the first time it gets it, and then gets its own Connection each time it gets it.

package com.csw.aoptransaction.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;


/**
 * @author Chen
 */
@Component
public class ConnectionUtil {
    private static HashMap<Thread, Connection> map = new HashMap<>();
    private static DataSource datasource;
    @Autowired
    public void setDatasource(DataSource datasource){
        ConnectionUtil.datasource = datasource;
    }

    public static Connection getConnection(Thread t) throws SQLException {
        Connection conn = null;
        if((conn = map.get(t)) == null){
            conn = datasource.getConnection();
            map.put(t,conn);
        }
        return conn;
    }
}

Mapper implementation

package com.csw.aoptransaction.mapper.Impl;

import com.csw.aoptransaction.bean.User;
import com.csw.aoptransaction.mapper.UserMapper;
import com.csw.aoptransaction.util.ConnectionUtil;
import org.springframework.stereotype.Repository;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

/**
 * @author Chen
 */
@Repository
public class UserMapperImpl implements UserMapper {

    @Override
    public int insertUser(User user) throws SQLException {
        Connection connection = ConnectionUtil.getConnection(Thread.currentThread());
        PreparedStatement ps = connection.prepareStatement("insert into t_user values(?,?,?)");
        ps.setInt(1,user.getId());
        ps.setString(2,user.getUsername());
        ps.setString(3,user.getPassword());
        return ps.executeUpdate();
    }
}

Service implementation

package com.csw.aoptransaction.service.Impl;

import com.csw.aoptransaction.bean.User;
import com.csw.aoptransaction.service.UserService;
import com.csw.aoptransaction.mapper.UserMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.sql.SQLException;

/**
 * @author Chen
 */
@Service
@Slf4j
public class UserServiceImpl implements UserService {
    @Resource
    private UserMapper userMapper;

    @Override
    public void insertUser(User user) throws SQLException {
        userMapper.insertUser(user);
    }

    @Override
    public void action(User... users) throws SQLException {
        for(User user : users){
            int i = userMapper.insertUser(user);
            if(i > 0){
                log.info(user.getUsername()+"Insertion temporarily succeeded");
            }
        }
    }
}

test result

Current table structure:

First Insert:



Second insert:
In order to simulate an anomalous rollback, the ID = 3 was intentionally inserted repeatedly with an error.

If the transaction is valid, the result is no insertion at all, if the transaction fails, the result is record insertion with ID=4 and ID=5, and insertion with ID=3 fails.

summary

Aop is a very useful tool to help us simplify the structure of our code. AOP can even be cut into annotations. Every place where annotations are marked can be cut in, so this also gives me an understanding of why Shiro can do privilege identification through annotations by using AOP to detect whether privileges are satisfied before interface calls. AOP is a powerful and useful tool.Hopefully you can continue to explore its usefulness.

Posted by patrick99e99 on Thu, 02 Sep 2021 10:58:42 -0700