What is the @ Transactional? Transactional @Transactional is Spring’s annotation for handling transactions, commit or rollback based on interceptors

Using the example

Here is an example of a method called addUser() annotated with the @Transactional annotation that calls another method called addUser2() with a NESTED isolation level


@Service
public class UserService {
    / /.. Omit the other
    @Transactional
    public void addUser(a) {
        userMapper.addItem("nA"."cA");
        // The addUser2() method is called in nested mode
        ((UserService) applicationContext.getBean(UserService.class)).addUser2();
        userMapper.addItem("nB"."cB");
    }

    @Transactional(propagation = Propagation.NESTED)
    public void addUser2(a) {
        userMapper.addItem("nC"."cC"); }}Copy the code

Execution process sequence diagram

The above example is actually two transactions. How to achieve this, see the steps below

The above example calls the execution step

  1. Intercept, because the Service class does not inherit an interface, into CglibAopProxy’s Intercept interceptor

  2. Call getInterceptorsAndDynamicInterceptionAdvice () method, and then generate the interceptor chain factory collection (interceptors list), then put the interceptor and target, and encapsulate an object CglibMethodInvocation, A call to PROCEED then performs the transaction logic

  3. At this point, the transaction logic is executed. Execute invokeWithinTransaction() through the TransactionInterceptor class, and then go to the TransactionAspectSupport class. Transaction logic included)

  4. If you go to the TransactionAspectSupport class, the logic is as follows (important)

  • 4.1 getTransaction() gets the transaction manager, and doGetTransaction() gets the transaction connection context. Save the thread context in ThreadLocal
  • 4.2 Determine whether a transaction exists. If so:
-PROPAGation_not_supported: No transaction is added. -PROPAGation_REQUIRES_new: Create a new transaction -PROPAGation_NESTED: creates a savepoint -PROPAGation_SUPPORTS or PROPAGATION_REQUIRED: merges an existing transactionCopy the code
  • 4.3 There is no transaction logic:
-PROPAGation_MANDATORY: throws exceptions. -PROPAGation_REQUIRED or PROPAGATION_REQUIRES_NEW or PROPAGATION_NESTED: Create a new transaction. The mysql commit is automatically closed and the current transaction is marked open. - Other propagation levels: Create an empty transactionCopy the code
  • 4.4 prepareTransactionInfo() binds transaction information TransactionInfo to the thread context
  1. Then execute the invocation. ProceedWithInvocation () to cut business logic code, performs to invokeJoinpoint () calls to business logic code, real is aboveUsing the exampleThe logic of the

@Service
public class UserService {
    / /.. Omit the other
    @Transactional
    public void addUser(a) {
        userMapper.addItem("nA"."cA");
        // The addUser2() method is called in nested mode
        ((UserService) applicationContext.getBean(UserService.class)).addUser2();
        userMapper.addItem("nB"."cB");
    }

    @Transactional(propagation = Propagation.NESTED)
    public void addUser2(a) {
        userMapper.addItem("nC"."cC"); }}Copy the code
  1. After that, go back to TransactionAspectSupport and perform commit or ROLLBACK logic
Con.mit () [normal, rollback the transaction]- if there are savepoints, rollback the transaction con.rollback()Copy the code

Source process

class CglibAopProxy implements AopProxy.Serializable {
    // omit other logic...
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        // Call Gglib dynamic proxy to omit other logic...
        retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
        // omit other logic...}}public abstract class TransactionAspectSupport implements BeanFactoryAware.InitializingBean {
    // omit other logic...
    // Create transaction (important !!!! Important !!!!)
    TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

    // The core method for creating a transaction is ====================================
    protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
                                                           @Nullable TransactionAttribute txAttr, final String joinpointIdentification) {
        // omit other logic...
        TransactionStatus status = null;
        if(txAttr ! =null) {
            if(tm ! =null) { status = tm.getTransaction(txAttr); }}PrepareTransactionInfo () binds transaction information TransactionInfo to the thread context, omits other logic...
        return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
    }

    // Get transaction
    public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) {
        // In Spring, doXxx does all the work, so this is the method to call the transaction
        Object transaction = doGetTransaction();
        // Handle transactions that already exist, such as the addUser2 call in the example
        if (isExistingTransaction(transaction)) {
            return handleExistingTransaction(def, transaction, debugEnabled);
        }
        // Transaction timeout, default no timeout limit
        if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
            throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
        }
        // PROPAGATION_MANDATORY Transactions are not allowed, and exceptions are thrown if any
        if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
            throw new IllegalTransactionStateException("No existing transaction found for transaction marked with propagation 'mandatory'");
        } else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
                def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
                def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
            // There are three types of transactions that can be created, PROPAGATION_REQUIRED, PROPAGATION_REQUIRES_NEW, and PROPAGATION_NESTED
            SuspendedResourcesHolder suspendedResources = suspend(null);
            // omit other code...
            return startTransaction(def, transaction, debugEnabled, suspendedResources);
        } else {
            // Create "empty" transaction: no actual transaction, but potentially synchronization.
            boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
            return prepareTransactionStatus(def, null.true, newSynchronization, debugEnabled, null); }}// Start a new transaction (called through the startTransaction() method above)
    protected void doBegin(Object transaction, TransactionDefinition definition) {
        DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;// Get the transaction data source
        Connection con = null;
        try {
            if(! txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) { Connection newCon = obtainDataSource().getConnection();if (logger.isDebugEnabled()) {
                    logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
                }
                // If there is no database connection, create a new one and put it in
                txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
            }
            // Set transaction synchronization, and get database Connection, and set other parameters
            txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
            con = txObject.getConnectionHolder().getConnection();
            Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
            txObject.setPreviousIsolationLevel(previousIsolationLevel);
            txObject.setReadOnly(definition.isReadOnly());
            // If automatic commit is enabled in mysql, turn off automatic commit and set the current transaction object autoCommit to true.
            if (con.getAutoCommit()) {
                txObject.setMustRestoreAutoCommit(true);
                con.setAutoCommit(false);
            }
            prepareTransactionalConnection(con, definition);
            txObject.getConnectionHolder().setTransactionActive(true);// Set transaction activation
            int timeout = determineTimeout(definition);
            if(timeout ! = TransactionDefinition.TIMEOUT_DEFAULT) { txObject.getConnectionHolder().setTimeoutInSeconds(timeout); }// Bind the connection holder to the thread.
            if(txObject.isNewConnectionHolder()) { TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder()); }}// omit other code...
    }

    // The getTransaction() method will follow this logic if it detects an existing transaction
    private TransactionStatus handleExistingTransaction(TransactionDefinition definition, Object transaction, boolean debugEnabled) {
        / / TransactionDefinition. PROPAGATION_NEVER: cannot exist, throw exceptions
        if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
            throw new IllegalTransactionStateException("Existing transaction found for transaction marked with propagation 'never'");
        }
        // Transactions are not supported, so the current transaction is suspended
        if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
            if (debugEnabled) {
                logger.debug("Suspending current transaction");
            }
            // What is the effect of transaction suspension?
           // - Method A calls method B, the Transaction opened by method A will be suspended, and any database operations in method B will not be managed by that Transaction
           // - How to suspend? Remove it from threadLocal (threadLocal 
      
       > resources) and store it in another data to avoid being committed
      
            Object suspendedResources = suspend(transaction);/ / hung
            boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
            return prepareTransactionStatus(definition, null.false, newSynchronization, debugEnabled, suspendedResources);
        }
        // Support for creating a new transaction
        if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
            if (debugEnabled) {
                logger.debug("Suspending current transaction, creating new transaction with name [" + definition.getName() + "]");
            }
            SuspendedResourcesHolder suspendedResources = suspend(transaction);// Schedule the current transaction and create a new one
            try {
                // Start something new
                return startTransaction(definition, transaction, debugEnabled, suspendedResources);
            } catch (RuntimeException | Error beginEx) {
                resumeAfterBeginException(transaction, suspendedResources, beginEx);
                throwbeginEx; }}// Support nested transactions
        if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
            if(! isNestedTransactionAllowed()) {// This is generally not not supported
                throw new NestedTransactionNotSupportedException(
                        "Transaction manager does not allow nested transactions by default - " +
                                "specify 'nestedTransactionAllowed' property with value 'true'");
            }
            if (debugEnabled) {
                logger.debug("Creating nested transaction with name [" + definition.getName() + "]");
            }
            if (useSavepointForNestedTransaction()) {
                // Create savepoint within existing Spring-managed transaction,
                // through the SavepointManager API implemented by TransactionStatus.
                // Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.
                DefaultTransactionStatus status =
                        prepareTransactionStatus(definition, transaction, false.false, debugEnabled, null);
                status.createAndHoldSavepoint();// a savepoint is created. Savepoints are released later when the transaction is committed
                return status;
            } else {
                // Only with JTA can you get to this step, there is no need to care
                return startTransaction(definition, transaction, debugEnabled, null); }}// Assumably PROPAGATION_SUPPORTS or PROPAGATION_REQUIRED.
        if (debugEnabled) {
            logger.debug("Participating in existing transaction");
        }
        if (isValidateExistingTransaction()) {
            if(definition.getIsolationLevel() ! = TransactionDefinition.ISOLATION_DEFAULT) { Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();if (currentIsolationLevel == null|| currentIsolationLevel ! = definition.getIsolationLevel()) { Constants isoConstants = DefaultTransactionDefinition.constants;throw new IllegalTransactionStateException("Participating transaction with definition [" +
                            definition + "] specifies isolation level which is incompatible with existing transaction: "+ (currentIsolationLevel ! =null ?
                                    isoConstants.toCode(currentIsolationLevel, DefaultTransactionDefinition.PREFIX_ISOLATION) :
                                    "(unknown)")); }}if(! definition.isReadOnly()) {if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
                    throw new IllegalTransactionStateException("Participating transaction with definition [" +
                            definition + "] is not marked as read-only but existing transaction is"); }}}booleannewSynchronization = (getTransactionSynchronization() ! = SYNCHRONIZATION_NEVER);return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null); }}// After execution, con.mit () or rollback is called
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager.Serializable {
    // omit other code......
    private void processCommit(DefaultTransactionStatus status) throws TransactionException {
        boolean unexpectedRollback = false;
        // omit other code
        prepareForCommit(status);
        triggerBeforeCommit(status);
        triggerBeforeCompletion(status);
        beforeCompletionInvoked = true;
        // If there is a savepoint, a savepoint is created when a transaction is nested. And then release
        if (status.hasSavepoint()) {
            if (status.isDebug()) {
                logger.debug("Releasing transaction savepoint");
            }
            unexpectedRollback = status.isGlobalRollbackOnly();
            status.releaseHeldSavepoint();// Release savepoint
        } else if (status.isNewTransaction()) {
            if (status.isDebug()) {
                logger.debug("Initiating transaction commit");
            }
            unexpectedRollback = status.isGlobalRollbackOnly();
            doCommit(status);// Commit the transaction
        } else if (isFailEarlyOnGlobalRollbackOnly()) {// Read-only transaction
            unexpectedRollback = status.isGlobalRollbackOnly();
        }
        if (unexpectedRollback) {
            throw new UnexpectedRollbackException(
                    "Transaction silently rolled back because it has been marked as rollback-only"); }}}Copy the code

conclusion

The transaction annotation is simple. The interceptor intercepts the annotation, loads the annotation parameter, and determines whether a transaction needs to be created based on the isolation level. If there are multiple transaction isolation levels, transaction savepoints will be involved, or transactions will be suspended between different transactions

  • Transaction savepoint: using the mysql database mechanism to roll back some logic. For example, the transaction has three time points (A->B1->B2->C), where B1->B2 is the transaction savepoint,

If B fails and does not throw an exception to the previous method, then only the SQL logic executed by B1->B2 will be rolled back. NESTED transactions depend on transaction savepoints, which is why the submethod with the exception is rolled back. The method that calls it does not roll back the transaction (normally the submethod removes the transaction savepoint after execution)

-- Usage is as follows:
BEGIN TRANSACTION A;
SELECT 2;
INSERT INTO TABLE1 (xx) VALUES ("xxx");
SAVE TRANSACTION B Point;  //B1 starts the transaction savepointINSERT INTO TABLE (xx) VALUES ("xxx");
ROLLBACK TRANSACTION B Point; //B2 rolls back data at the transaction savepointSELECT 1;
INSERT INTO TABLE2 (xx) VALUES ("xxx");
COMMIT TRANSACTION A;
Copy the code
  • Transaction suspension: A transaction suspension is simple enough to remove data from treadLocal and temporarily store it elsewhere to avoid automatic commit