This is the first day of my participation in Gwen Challenge

This article mainly introduces the recent Spring transaction did not work problems

1. Take a look at Spring’s transaction isolation level

Transaction isolation level describe
REQUIRED Supports the current transaction or creates a new one if one does not exist.

This is the default setting for transaction annotations.
SUPPORTS Supports the current transaction and, if none exists, executes it nontransactionally.
MANDATORY Supports the current transaction and throws an exception if none exists.
REQUIRES_NEW Create a new transaction and suspend the current transaction, if it exists.
NOT_SUPPORTED Executes nontransactionally, suspending the current transaction if one exists.
NEVER Executes nontransactionally, throwing an exception if a transaction exists.
NESTED If the current transaction exists, it is executed in a nested transaction, otherwise it is executed as required.

2. Self-invoking transaction failure scenario in the same class

/** * ServiceA */
class ServiceA {

    @Transactional(rollbackFor = Exception.class)
    public void methodA(a) {
        // Start another transaction in methodB
        methodB();
    }

    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    public void methodB(a) {}}Copy the code

The ServiceA class methodA starts a transaction, calls methodB in methodA, suspends the current transaction and starts a new transaction in methodB, but methodB’s transaction does not actually take effect.

3. Cause of transaction failure

Because Spring transactions are implemented through dynamic proxies, when the proxy calls methodA, it actually executes the methodB method of the source object and does not execute the methodB method of the proxy object, so the methodB transaction does not take effect

See the proxy object generated by ServiceA below (the code is omitted for simplicity)

/** * ServiceA proxy object */
class ServiceAProxy {

    // Service A
    private ServiceA serviceA;

    // methodA proxy method
    public void methodA(a) {
        // Start the transaction
        try {
            serviceA.methodA();
        } catch (Throwable t) {
            // Transaction rollback
        }
        // End of transaction
    }

    // methodB proxy method
    public void methodB(a) {
        // Start the transaction
        try {
            serviceA.methodB();
        } catch (Throwable t) {
            // Transaction rollback
        }
        // End of transaction}}Copy the code

4. Solutions

4.1 The object to be called injects itself

/** * ServiceA */
class ServiceA {
    
    @Autowired
    private ServiceA serviceA;

    @Transactional(rollbackFor = Exception.class)
    public void methodA(a) {
        // Start another transaction in methodB
        serviceA.methodB();
    }

    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    public void methodB(a) {}}Copy the code

4.2 Move a self-called method out of another class

/** * ServiceA */
class ServiceA {
    
    @Autowired
    private ServiceB serviceB;

    @Transactional(rollbackFor = Exception.class)
    public void methodA(a) {
        // Start another transaction in methodBserviceB.methodB(); }}/** * ServiceB */
class ServiceB {

    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    public void methodB(a) {}}Copy the code

4.3 Fetch the current proxy object using AopContext

/** * ServiceA */
// Put the proxy class into the thread context
@EnableAspectJAutoProxy(exposeProxy = true)
class ServiceA {

    @Transactional(rollbackFor = Exception.class)
    public void methodA(a) {
        // Start another transaction in methodB
        ((ServiceA)AopContext.currentProxy()).methodB();
    }

    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    public void methodB(a) {}}Copy the code