Database transactions

A database Transaction (TX) is a logical unit of DBMS execution, an atomic unit of work that can be committed or rolled back. When a transaction makes multiple changes to the database, either all changes are successful when the transaction is committed or all changes are undone when the transaction is rolled back.

Database (including but not limited to relational) transactions generally have the following four characteristics, known as ACID properties

ACID

  • Atomicity: The transaction is executed as a whole, and all or none of the operations on the database contained within it are executed.
  • Consistency: Transactions should ensure that the state of the database changes from one consistent state to another. _ Consistent state _ indicates that data in the database must meet integrity constraints.
  • Isolation: When multiple transactions are executed concurrently, the execution of one transaction should not affect the execution of other transactions.
  • Durability: Modifications to the database by committed transactions should be permanently saved in the database.

Transactions in Mysql

START TRANSACTION [transaction_characteristic [, transaction_characteristic] ...]  transaction_characteristic: { WITH CONSISTENT SNAPSHOT | READ WRITE | READ ONLY } BEGIN [WORK] COMMIT [WORK] [AND [NO] CHAIN] [[NO] RELEASE] ROLLBACK [WORK] [AND [NO] CHAIN] [[NO] RELEASE] SET autocommit = {0 | 1}Copy the code
  • START TRANSACTIONorBEGINStart a new transaction.
  • COMMITCommit the current transaction.
  • ROLLBACKRoll back the current transaction.
  • SET autocommitDisables or enables the default auto commit mode for the current session.

By default, Mysql is in auto-commit mode, all statements are committed immediately

Transactions in JDBC

JDBC is an application program interface in the Java language that regulates how a client program accesses a database. JDBC provides methods for querying and updating data in a database. JDBC, also a trademark of Sun Microsystems (now owned by Oracle), is relational-database oriented.

In JDBC, automatic commit is disabled as the first step in a transaction transaction:

con.setAutoCommit(false);

Copy the code

Commit transaction:

con.commit();

Copy the code

Rollback transaction:

con.rollback();

Copy the code

An example of a complete process (from the Oracle JDBC documentation) :

public void updateCoffeeSales(HashMap<String, Integer> salesForWeek) throws SQLException { PreparedStatement updateSales = null; PreparedStatement updateTotal = null; String updateString = "update " + dbName + ".COFFEES " + "set SALES = ? where COF_NAME = ?" ; String updateStatement = "update " + dbName + ".COFFEES " + "set TOTAL = TOTAL + ? " + "where COF_NAME = ?" ; try { con.setAutoCommit(false); updateSales = con.prepareStatement(updateString); updateTotal = con.prepareStatement(updateStatement); for (Map.Entry<String, Integer> e : salesForWeek.entrySet()) { updateSales.setInt(1, e.getValue().intValue()); updateSales.setString(2, e.getKey()); updateSales.executeUpdate(); updateTotal.setInt(1, e.getValue().intValue()); updateTotal.setString(2, e.getKey()); updateTotal.executeUpdate(); con.commit(); } } catch (SQLException e ) { JDBCTutorialUtilities.printSQLException(e); if (con ! = null) { try { System.err.print("Transaction is being rolled back"); con.rollback(); } catch(SQLException excep) { JDBCTutorialUtilities.printSQLException(excep); } } } finally { if (updateSales ! = null) { updateSales.close(); } if (updateTotal ! = null) { updateTotal.close(); } con.setAutoCommit(true); }}Copy the code

Why do YOU need a transaction manager

Without a transaction manager, our program might look like this:

Connection connection = acquireConnection();
try{
    int updated = connection.prepareStatement().executeUpdate();
    connection.commit();
}catch (Exception e){
    rollback(connection);
}finally {
    releaseConnection(connection);
}

Copy the code

Or it could be something like this:

execute(new TxCallback() { @Override public Object doInTx(Connection var1) { //do something... return null; }}); public void execute(TxCallback txCallback){ Connection connection = acquireConnection(); try{ txCallback.doInTx(connection); connection.commit(); }catch (Exception e){ rollback(connection); }finally { releaseConnection(connection); }} # lambda version execute(connection -> {//do something... return null; });Copy the code

However, the above two methods are very inconvenient for some complex scenarios. In a real business scenario, there is a lot of complex business logic, long code, complex logic connections, and if a big operation is full of this kind of code, I think developers will go crazy. Not to mention the custom isolation levels and the handling of nested/isolated transactions.

Introduction to Spring Transaction Manager

Spring is the strongest Java framework, and transaction management is one of its core functions. Spring provides a uniform abstraction for transaction management with the following benefits:

  • A consistent programming model across different transaction apis such as Java Transaction API (JTA), JDBC, Hibernate, Java Persistence API (JPA), and Java Data Objects (JDO).
  • Support for declarative transaction management (annotated form)
  • Apis for programmatic transaction management are simpler than complex transaction apis such as JTA
  • And Spring abstract Data layer integration is convenient (such as Spring Hibernate/Jdbc/Mybatis/Jpa…).

Spring’s transaction manager is just an interface/abstraction, and different DB-layer frameworks (not just DB-class frameworks, but also the transaction model support theory can use this abstraction) may need to implement this standard to work better. Core interface is org. Springframework. Transaction. Support. AbstractPlatformTransactionManager, its code is located in the spring – tx module, as in the implementation of the Hibernate as follows: org.springframework.orm.hibernate4.HibernateTransactionManager

use

Transactions, naturally, control the business, and within a business process, often want to ensure atomicity, all or nothing.

Therefore, a transaction usually loads the @Service layer. Within a Service, multiple operations (such as Dao) that operate the database are called. After the Service ends, the transaction is automatically committed, and the transaction is rolled back if an exception is thrown.

This is also the basic usage principle of Spring transaction management.

annotations

Add the @Transactional annotation to a spring-managed class to enable transaction management for all methods under that class. After the transaction is enabled, operations within the method do not need to manually start/commit/roll back the transaction.

@Service @Transactional public class TxTestService{ @Autowired private OrderRepo orderRepo; public void submit(Order order){ orderRepo.save(order); }}Copy the code

It can also be configured only on methods, which take precedence over classes

@Service public class TxTestService{ @Autowired private OrderRepo orderRepo; @Transactional public void submit(Order order){ orderRepo.save(order); }}Copy the code

TransactionTemplate

The TransactionTemplate approach is not much different from using annotations, but the core function is also implemented by TransactionManager, with a different entry point

public <T> T execute(TransactionCallback<T> action) throws TransactionException { if (this.transactionManager instanceof  CallbackPreferringPlatformTransactionManager) { return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action); } else {/ / transaction information TransactionStatus status = this. TransactionManager. GetTransaction (this); T result; Result = action.doinTransaction (status); } // The Transactional code threw Application Exception -> rollback rollbackOnException(status, ex); throw ex; } catch (Error err) { // Transactional code threw error -> rollback rollbackOnException(status, err); throw err; } catch (Exception ex) { // Transactional code threw unexpected exception -> rollback rollbackOnException(status, ex); throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception"); } / / to commit the transaction this.transactionManager.com MIT (status); return result; }}Copy the code

The XML configurationtx:advice

Too old to explain

Isolation Level

The transaction isolation level is one of the most important features of a database, ensuring that problems such as dirty/phantom reads do not occur. As a transaction management framework is also support this configuration, a isolation in @ Transactional annotation configuration, can easily configure each transaction isolation level, equal to the connection. SetTransactionIsolation at ()

Isolation {
    DEFAULT(-1),
    READ_UNCOMMITTED(1),
    READ_COMMITTED(2),
    REPEATABLE_READ(4),
    SERIALIZABLE(8);
}

Copy the code

Propagation Behavior

People who are not familiar with Spring may wonder what this is when they hear about propagation behavior.

In fact, this propagation behavior has nothing to do with database functionality, but is a mechanism designed by the transaction manager to handle complex business.

For example, A Service -> B Service -> C Service is called, but A/B is in the same transaction, C is an independent transaction, and if C fails, the transaction where AB is located will not be affected.

At this point, it can be dealt with by spreading behavior; Configure the C Service transaction to @Transactional(Propagation = Propagation.requires_new)

Spring supports the following propagation behaviors:

REQUIRED

The default policy is to use the current transaction (and the transaction resources bound by the current thread) first, or start a new transaction if none exists

SUPPORTS

The current transaction (and the transaction resources bound by the current thread) is used preferentially, and if none exists, it is run transaction-free

MANDATORY

The current transaction is preferred, and an exception is thrown if none exists

REQUIRES_NEW

Creates a new transaction and suspends (Suspend) if the current transaction exists

NOT_SUPPORTED

Executes nontransactionally, suspends the current transaction if it exists.

NEVER

Executes nontransactionally, throwing an exception if the current transaction exists

The rollback strategy

There are four attributes in @Transactional that configure Rollback policies, including the Rollback and NoRollback policies

By default, RuntimeException and Error cause the transaction to be rolled back. Normal exceptions (which require a Catch) do not.

Rollback

Configure the exception class to be rolled back

Class Class<? extends Throwable>[] rollbackFor() default {}; [] rollbackForClassName() default {};Copy the code

NoRollback

For business logic that needs special processing, such as inserting log tables, or unimportant business processes, it is hoped that the transaction will not be affected if errors occur.

This can be done by configuring NoRollbackFor so that certain exceptions do not affect the state of the transaction.

Class Class<? extends Throwable>[] noRollbackFor() default {}; FullName/SimpleName String[] noRollbackForClassName() default {};Copy the code

Read-only control

Set the read-only identifier for the current transaction, equivalent to Connection.setreadonly ()

Explanation of key nouns

noun concept
PlatformTransactionManager Transaction manager, which manages the various life-cycle methods of a transaction, TxMgr for short
TransactionAttribute Transaction property, which contains information such as isolation level, propagation behavior, and whether it is read-only, referred to as TxAttr
TransactionStatus Transaction status, including the current transaction, pending information, referred to as TxStatus
TransactionInfo Transaction information, containing TxMgr, TxAttr, TxStatus and other information, referred to as TxInfo
TransactionSynchronization A transaction synchronization callback containing multiple hook methods, called TxSync/Transaction synchronization
TransactionSynchronizationManager Transaction synchronization manager, maintains current thread transaction resources, information and TxSync collection

The basic principle of

Public void execute(TxCallback TxCallback){Connection Connection = acquireConnection(); Try {// execute the business code doInService(); // submit transaction connection.mit (); }catch (Exception e){// Rollback transaction (connection); }finally {// releaseConnection(connection); }}Copy the code

The basic principle of Spring transaction management is the code above, get connection -> execute code -> commit/rollback transaction. Spring simply abstracts the process away, leaving all transaction-related operations to TransactionManager and encapsulating a template-like entry to execute

Such as org. Springframework. Transaction. Support. TransactionTemplate implementation:

@Override public <T> T execute(TransactionCallback<T> action) throws TransactionException { if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) { return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action); } else {/ / by the transaction manager to transaction TransactionStatus status = this. TransactionManager. GetTransaction (this); T result; Result = action.doinTransaction (status); } // The Transactional code threw Application Exception -> rollback rollbackOnException(status, ex); throw ex; } catch (Error err) { // Transactional code threw error -> rollback rollbackOnException(status, err); throw err; } catch (Exception ex) { // Transactional code threw unexpected exception -> rollback rollbackOnException(status, ex); throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception"); } / / to commit the transaction this.transactionManager.com MIT (status); return result; }}Copy the code

Transactional annotation (@Transactional) implements the same mechanism. Spring-based AOP replaces the Template pattern above with automatic AOP. In an AOP Interceptor (org. Springframework. Transaction. The Interceptor. TransactionInterceptor) to perform in this process:

protected Object invokeWithinTransaction(Method method, Class<? > targetClass, final InvocationCallback invocation) throws Throwable { // If the transaction attribute is null, the method is non-transactional. final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass); / / get the transaction manager final PlatformTransactionManager tm = determineTransactionManager (txAttr); final String joinpointIdentification = methodIdentification(method, targetClass); if (txAttr == null || ! (tm instanceof CallbackPreferringPlatformTransactionManager)) { // Standard transaction demarcation with getTransaction And commit/rollback calls. / / create a transaction TransactionInfo txInfo = createTransactionIfNecessary (tm, txAttr. joinpointIdentification); Object retVal = null; try { // This is an around advice: Invoke the next interceptor in the chain. // This will normally result in a target object being invoked. // Execute the code "AOP" retVal = invocation.proceedWithInvocation(); } the catch (Throwable ex) {/ / target invocation exception exception handling / / rollback completeTransactionAfterThrowing (txInfo, ex); throw ex; } finally {// Clean the resource cleanupTransactionInfo(txInfo); } / / to commit the transaction commitTransactionAfterReturning (txInfo); return retVal; }... }Copy the code

Key to transaction propagation/keeping the same transaction in complex processes:

For more complex business processes, where various class invocations occur, how does Spring maintain the same transaction?

The Connection is implicitly stored in the transaction manager, and subsequent methods are retrieved from the transaction manager before executing JDBC operations:

Such as getCurrentSession in SessionFactory in HibernateTemplate, which is obtained (possibly indirectly) from the Spring transaction manager

The Spring transaction manager will hold relevant temporary resources (connections, etc.) while the transaction is being processedorg.springframework.transaction.support.TransactionSynchronizationManagerIs maintained through ThreadLocal

public abstract class TransactionSynchronizationManager { private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class); private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<Map<Object, Object>>("Transactional resources"); private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal<Set<TransactionSynchronization>>("Transaction synchronizations"); private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal<String>("Current transaction name"); private static final ThreadLocal<Boolean> currentTransactionReadOnly = new NamedThreadLocal<Boolean>("Current transaction read-only status"); private static final ThreadLocal<Integer> currentTransactionIsolationLevel = new NamedThreadLocal<Integer>("Current transaction isolation level"); private static final ThreadLocal<Boolean> actualTransactionActive = new NamedThreadLocal<Boolean>("Actual transaction active"); . }Copy the code

For some complex scene, a nested transaction + independent transaction, hanging (suspend) involves the recovery of (resume), the related resources is also stored in TransactionSynchronizationManager * * * *, convenient nested transaction processing.

Such as A – > B, A method has opened up the transaction, and the current transaction resource binding in TransactionSynchronizationManager, so B before, will detect whether the current existing transaction; Detection way is from TransactionSynchronizationManager find and test status, if you already have within the transaction, then according to the different propagation behavior configured to perform different logic, such as for the REQUIRES_NEW spread behavior of the process will be some trouble, Suspend and resume operations are involved, and they work in much the same way that I won’t explain here

Q&A

Transaction not effective

The testTx method uses the @Transactional annotation as its entry point, and throws a RuntimeException after the data is inserted. However, the data is not rolled back

public void test(){ testTx(); } @Transactional public void testTx(){ UrlMappingEntity urlMappingEntity = new UrlMappingEntity(); urlMappingEntity.setUrl("http://www.baidu.com"); urlMappingEntity.setExpireIn(777l); urlMappingEntity.setCreateTime(new Date()); urlMappingRepository.save(urlMappingEntity); if(true){ throw new RuntimeException(); }}Copy the code

The reason it doesn’t work here is because the incoming methods/classes don’t add @Transaction annotations, and because Spring’s Transaction manager is AOP based, both Cglib(ASM) and Jdk dynamic proxies are essentially subclass mechanisms; Method calls between classes call the code of the class directly, without executing the code of the dynamic proxy. So in this example, the transaction annotations added to the textTx method will not take effect because the test entry method does not add a proxy annotation

The transaction fails after asynchrony

For example, in a transaction method, if the child thread operation library is enabled, the child thread’s transaction and the main thread’s transaction are different.

Because in the Spring of the transaction manager, affairs related resources (connection, session, transaction state, etc.) are stored in the TransactionSynchronizationManager, via ThreadLocal to store, You can’t guarantee a transaction if it’s across threads

# TransactionSynchronizationManager.java private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources"); private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal<>("Transaction  synchronizations"); private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal<>("Current transaction name"); private static final ThreadLocal<Boolean> currentTransactionReadOnly = new NamedThreadLocal<>("Current transaction read-only status"); private static final ThreadLocal<Integer> currentTransactionIsolationLevel = new NamedThreadLocal<>("Current transaction  isolation level"); private static final ThreadLocal<Boolean> actualTransactionActive = new NamedThreadLocal<>("Actual transaction active");Copy the code

Transaction commit failed

org.springframework.transaction.UnexpectedRollbackException: 
Transaction silently rolled back because it has been marked as rollback-only

Copy the code

This exception is caused by a child method throwing an exception that is ignored by the parent method when called between multiple transaction methods within the same transaction.

Because the method throws exceptions, Spring transaction manager will be labelled the current transaction status for failure, ready to roll back, but when the stack method has been completed, after the parent method ignores this exception, stay after the method performs normal submission, the transaction manager will check the rollback status, if a rollback will throw this exception. Specific can consult org. Springframework. Transaction. Support. AbstractPlatformTransactionManager# processCommit

Sample code:

A -> B # A Service(@Transactional): public void testTx(){ urlMappingRepo.deleteById(98l); try{ txSubService.testSubTx(); }catch (Exception e){ e.printStackTrace(); } } # B Service(@Transactional) public void testSubTx(){ if(true){ throw new RuntimeException(); }}Copy the code

reference

  • Docs. Spring. IO/spring/docs…
  • Docs.oracle.com/javase/tuto…
  • www.cnblogs.com/micrari/p/7…