Spring transaction details

Posted by hadingrh on Fri, 24 May 2019 02:04:42 +0200

Before writing this blog, I first read Spring in action, and then read some articles about Spring transaction management on the internet. I don't feel that they are complete. Here, I will summarize the knowledge of the book and the Internet about the transaction. The reference articles are as follows:

1 Preliminary understanding

Before you understand things, talk about one of the most common things you do in your daily life: withdrawing money.  
For example, if you go to ATM to get 1000 yuan, there are roughly two steps: first, enter the password amount, the bank card deducts 1000 yuan; then ATM gives 1000 yuan. These two steps must either be executed or not. If the bank card deducts 1000 yuan but ATM fails to pay, you will lose 1000 yuan; if the bank card fails to deduct money but ATM gives 1000 yuan, the bank will lose 1000 yuan. So, if one step succeeds and the other fails, it's not good for both sides. If no matter which step fails, the whole process of withdrawing money can be rolled back, that is to say, all operations can be cancelled completely, it's very good for both sides.  
Transactions are used to solve similar problems. Transactions are a series of actions, which are integrated into a complete unit of work. These actions must be completed. If one fails, the transaction will roll back to its original state as if nothing had happened.  
In enterprise application development, transaction management is an indispensable technology to ensure data integrity and consistency.  
Transactions have four characteristics: ACID

  • Atomicity: A transaction is an atomic operation consisting of a series of actions. The atomicity of the transaction ensures that the action is either complete or completely ineffective.
  • Consistency: Once a transaction is completed (whether successful or unsuccessful), the system must ensure that the business it models is in a consistent state, not a partial completion failure. Data in reality should not be destroyed.
  • Isolation: There may be many transactions that process the same data at the same time, so each transaction should be isolated from other transactions to prevent data corruption.
  • Durability: Once a transaction is completed, no matter what system error occurs, its results should not be affected, so that it can recover from any system crash. Usually, the result of a transaction is written to a persistent memory.

2 Core Interface

There are many details about the implementation of Spring transaction management. If we have a general understanding of the whole interface framework, it will be very helpful for us to understand the transaction. Here we will understand the specific strategy of Spring transaction implementation by explaining Spring's transaction interface.  
The interfaces involved in Spring transaction management are as follows:

2.1 Transaction Manager

Spring does not directly manage transactions, but provides a variety of transaction managers, which delegate the responsibility of transaction management to the transactions of relevant platform frameworks provided by persistence mechanisms such as Hibernate or JTA.  
The interface of Spring Transaction Manager is org. spring framework. transaction. Platform Transaction Manager. Through this interface, Spring provides corresponding transaction managers for various platforms such as JDBC, Hibernate, etc., but the specific implementation is the business of each platform itself. The contents of this interface are as follows:

Public interface PlatformTransactionManager()...{  
    // Getting the TransactionStatus object from TransactionDefinition
    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; 
    // Submission
    Void commit(TransactionStatus status) throws TransactionException;  
    // RollBACK
    Void rollback(TransactionStatus status) throws TransactionException;  
 } 

 

It can be seen from this that the specific transaction management mechanism is transparent to Spring, and it does not care about those, which need to be concerned about the corresponding platforms, so one advantage of Spring transaction management is to provide a consistent programming model for different transaction API s, such as JTA, JDBC, Hibernate, JPA. Next, we introduce the mechanism of transaction management in each platform framework.

2.1.1 JDBC transactions

If the application uses JDBC directly for persistence, the Data Source Transaction Manager handles transaction boundaries for you. To use DataSource Transaction Manager, you need to assemble it into the context definition of the application using the following XML:

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
</bean>

In fact, the DataSource Transaction Manager manages transactions by calling java.sql.Connection, which is obtained through DataSource. A transaction is committed by calling the commit() method of the connection. Similarly, a transaction failure is rolled back by calling the rollback() method.

2.1.2 Hibernate transaction

If application persistence is interned through Hibernate, then you need to use Hibernate Transaction Manager. For Hibernate3, you need to add the following < bean > declaration to the Spring context definition:

<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
</bean>

 

The session Factory attribute requires assembling a Hibernate session factory. The implementation details of Hibernate Transaction Manager are that it delegates the responsibility of transaction management to the org.hibernate.Transaction object, which is obtained from Hibernate Session. When the transaction completes successfully, HibernateTransaction Manager calls the commit() method of the Transaction object, and vice versa, the rollback() method.

2.1.3 Java Persistence API Transactions (JPA)

Hibernate has been the de facto Java persistence standard for many years, but now the Java persistence API has come into everyone's view as a true Java persistence standard. If you plan to use JPA, you need to use Spring's Jpa Transaction Manager to handle transactions. You need to configure JpaTransaction Manager in Spring as follows:

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

JpaTransaction Manager only needs to assemble a JPA entity management factory (any implementation of the javax.persistence.EntityManagerFactory interface). JpaTransaction Manager will work with JPA Entity Manager generated by the factory to build transactions.

2.1.4 Java native API transactions

If you do not use the transaction management described above, or cross multiple transaction management sources (such as two or more different data sources), you need to use JtaTransaction Manager:

    <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
        <property name="transactionManagerName" value="java:/TransactionManager" />
    </bean>

 

JtaTransaction Manager delegates responsibility for transaction management to javax.transaction.UserTransaction and javax. transaction. Transaction Manager objects, where transactions are successfully completed and submitted through the UserTransaction.commit() method, and transaction failures are rolled back through the UserTransaction.rollback() method.

2.2 Definition of basic transaction attributes

The aforementioned transaction manager interface Platform Transaction Manager obtains transactions through getTransaction (Transaction Definition definition) method, in which the parameter is the Transaction Definition class, which defines some basic transaction attributes.  
So what are transaction attributes? Transaction attributes can be understood as some basic configurations of transactions, describing how transaction strategies are applied to methods. Transaction attributes include five aspects, as shown in the figure:

The Transaction Definition interface is as follows:

public interface TransactionDefinition {
    int getPropagationBehavior(); // Propagation behavior of return transactions
    int getIsolationLevel(); // Returns the isolation level of a transaction, according to which the transaction manager controls what data can be seen in another transaction
    int getTimeout();  // How many seconds must the return transaction be completed
    boolean isReadOnly(); // Whether a transaction is read-only or not, the transaction manager can optimize it based on this return value to ensure that the transaction is read-only
} 

We can see that Transaction Definition is just used to define transaction attributes. The following details describe each transaction attributes.

2.2.1 Communication Behavior

The first aspect of a transaction is propagation behavior. When a transaction method is called by another transaction method, you must specify how the transaction should be propagated. For example, a method may continue to run in an existing transaction, or it may open a new transaction and run in its own transaction. Spring defines seven communication behaviors:

Communication behavior Meaning
PROPAGATION_REQUIRED Represents that the current method must run in a transaction. If the current transaction exists, the method will run in that transaction. Otherwise, a new transaction will be started
PROPAGATION_SUPPORTS Represents that the current method does not need a transaction context, but if there is a current transaction, the method runs in that transaction.
PROPAGATION_MANDATORY Represents that the method must run in a transaction, and if the current transaction does not exist, an exception is thrown
PROPAGATION_REQUIRED_NEW Represents that the current method must run in its own transaction. A new transaction will be started. If there is a current transaction, the current transaction will be suspended during the execution of the method. If you use JTA Transaction Manager, you need to access Transaction Manager
PROPAGATION_NOT_SUPPORTED Represents that the method should not run in a transaction. If there is a current transaction, the current transaction will be suspended during the operation of the method. If you use JTA Transaction Manager, you need to access Transaction Manager
PROPAGATION_NEVER Represents that the current method should not run in a transaction context. If a transaction is currently running, an exception is thrown
PROPAGATION_NESTED Represents that if a transaction already exists, the method will run in a nested transaction. Nested transactions can be submitted or rolled back independently of the current transaction. If the current transaction does not exist, it behaves as PROPAGATION_REQUIRED. Attention should be paid to the differences among manufacturers in their support of this communication behavior. You can refer to the resource manager's documentation to confirm whether they support nested transactions.


Note: The following specific explanations on communication behavior are referenced from Detailed explanation of Spring transaction mechanism 
(1) PROPAGATION_REQUIRED supports the current transaction if a transaction exists. If there are no transactions, start a new transaction.

//Transaction attribute PROPAGATION_REQUIRED
methodA{
    ......
    methodB();
    ......
}


//Transaction attribute PROPAGATION_REQUIRED
methodB{
   ......
}

 

Using spring declarative transactions, spring uses AOP to support declarative transactions. According to transaction attributes, spring automatically decides whether to open a transaction before method invocation, and decides whether to commit or roll back a transaction after method execution.

Call the methodB method separately:

main{ 
    metodB(); 
} 

Amount to

Main{ 
    Connection con=null; 
    try{ 
        con = getConnection(); 
        con.setAutoCommit(false); 

        //Method call
        methodB(); 

        //Submission of affairs
        con.commit(); 
    } Catch(RuntimeException ex) { 
        //Rollback transaction
        con.rollback();   
    } finally { 
        //Release resources
        closeCon(); 
    } 
} 

 

Spring ensures that all calls in the methodB method get the same connection. When methodB is called, no transaction exists, so a new connection is obtained and a new transaction is opened.  
When MethodA is invoked alone, MethodB is invoked in MethodA.

The implementation effect is equivalent to:

main{ 
    Connection con = null; 
    try{ 
        con = getConnection(); 
        methodA(); 
        con.commit(); 
    } catch(RuntimeException ex) { 
        con.rollback(); 
    } finally {    
        closeCon(); 
    }  
} 

When MethodA is invoked, there are no transactions in the environment, so a new transaction is started. When MethodB is invoked in MethodA, there is already a transaction in the environment, so methodB joins the current transaction.

(2) PROPAGATION_SUPPORTS supports the current transaction if a transaction exists. If there are no transactions, then non-transaction execution. However, PROPAGATION_SUPPORTS is slightly different from Transaction-Free transaction managers for transaction synchronization.

//Transaction attribute PROPAGATION_REQUIRED
methodA(){
  methodB();
}

//Transaction attribute PROPAGATION_SUPPORTS
methodB(){
  ......
}

When methodB is simply invoked, the methodB method is non-transactional. When methdA is called, methodB joins the transaction of methodA and executes transactionally.

(3) PROPAGATION_MANDATORY supports the current transaction if a transaction already exists. If there is no active transaction, an exception is thrown.

//Transaction attribute PROPAGATION_REQUIRED
methodA(){
    methodB();
}

//Transaction attribute PROPAGATION_MANDATORY
    methodB(){
    ......
}

 

When methodB is invoked alone, because there is currently no active transaction, an exception throw new Illegal Transaction StateException ("Transaction propagation'mandatory'but no existing transaction found") is thrown; when methodA is invoked, methodB joins the transaction of methodA and executes transactionally.

(4) PROPAGATION_REQUIRES_NEW always opens a new transaction. If a transaction already exists, suspend the existing transaction.

//Transaction attribute PROPAGATION_REQUIRED
methodA(){
    doSomeThingA();
    methodB();
    doSomeThingB();
}

//Transaction attribute PROPAGATION_REQUIRES_NEW
methodB(){
    ......
}

 

Call method A:

main(){
    methodA();
}

Amount to

main(){
    TransactionManager tm = null;
    try{
        //Get a JTA transaction manager
        tm = getTransactionManager();
        tm.begin();//Open a new transaction
        Transaction ts1 = tm.getTransaction();
        doSomeThing();
        tm.suspend();//Suspend the current transaction
        try{
            tm.begin();//Restart the second transaction
            Transaction ts2 = tm.getTransaction();
            methodB();
            ts2.commit();//Submit a second transaction
        } Catch(RunTimeException ex) {
            ts2.rollback();//Roll back the second transaction
        } finally {
            //Release resources
        }
        //After methodB is executed, the first transaction is restored
        tm.resume(ts1);
        doSomeThingB();
        ts1.commit();//Submit the first transaction
    } catch(RunTimeException ex) {
        ts1.rollback();//Roll back the first transaction
    } finally {
        //Release resources
    }
}

 

Here, I call TS1 outer transaction and TS2 inner transaction. As can be seen from the above code, TS2 and TS1 are two separate transactions, independent of each other. The success of Ts2 does not depend on ts1. If the doSomeThingB method of the methodA method fails after calling the methodB method, the results of the methodB method are still submitted. The result of code other than methodB is rolled back. Using PROPAGATION_REQUIRES_NEW, you need to use JtaTransaction Manager as the transaction manager.

(5) PROPAGATION_NOT_SUPPORTED is always executed non-transactionally and suspends any existing transactions. Using PROPAGATION_NOT_SUPPORTED, you also need to use JtaTransaction Manager as transaction manager. (Same code example as above, can be deduced in the same way)

(6) PROPAGATION_NEVER is always executed non-transactionally and throws an exception if there is an active transaction.

(7) PROPAGATION_NESTED runs in a nested transaction if an active transaction exists. If no active transaction exists, it executes according to the Transaction Definition. PROPAGATION_REQUIRED attribute. This is a nested transaction. When using JDBC 3.0 driver, only DataSource Transaction Manager is supported as the transaction manager. The java.sql.Savepoint class driven by JDBC is required. Some JTA transaction manager implementations may provide the same functionality. Using PROPAGATION_NESTED, you also need to set the nested Transaction Allowed attribute of Platform Transaction Manager to true, while the nested Transaction Allowed attribute value defaults to false.

//Transaction attribute PROPAGATION_REQUIRED
methodA(){
    doSomeThingA();
    methodB();
    doSomeThingB();
}

//Transaction attribute PROPAGATION_NESTED
methodB(){
    ......
}

 

If the method B method is invoked separately, it is executed according to the REQUIRED attribute. If the methodA method is called, the effect is as follows:

main(){
    Connection con = null;
    Savepoint savepoint = null;
    try{
        con = getConnection();
        con.setAutoCommit(false);
        doSomeThingA();
        savepoint = con2.setSavepoint();
        try{
            methodB();
        } catch(RuntimeException ex) {
            con.rollback(savepoint);
        } finally {
            //Release resources
        }
        doSomeThingB();
        con.commit();
    } catch(RuntimeException ex) {
        con.rollback();
    } finally {
        //Release resources
    }
}

 

Before the method B method is called, the setSavepoint method is called to save the current state to savepoint. If the methodB method call fails, it restores to the previously saved state. However, it should be noted that the transaction is not committed at this time, and if the subsequent code (doSomeThingB() method) call fails, the rollback includes all operations of the method B method.

A very important concept of nested transactions is that internal transactions depend on external transactions. When the outer transaction fails, the actions of the inner transaction are rolled back. Failure of internal transaction operation does not cause rollback of external transaction.

The difference between PROPAGATION_NESTED and PROPAGATion_REQUIRES_NEW is that they are very similar, they are all like nested transactions, and if there is no active transaction, they will open a new transaction. When using PROPAGATION_REQUIRES_NEW, the inner transaction and the outer transaction are just like two separate transactions. Once the inner transaction is committed, the outer transaction cannot roll back. The two transactions do not affect each other. Two transactions are not really nested transactions. At the same time, it needs the support of JTA transaction manager.

When using PROPAGATION_NESTED, the rollback of outer transaction can cause the rollback of inner transaction. The exception of the inner transaction does not cause the rollback of the outer transaction, it is a real nested transaction. When Data Source Transaction Manager uses savepoint to support PROPAGATION_NESTED, it needs JDBC driver above 3.0 and JDK version support above 1.4. Other JTA Trasaction Manager implementations may have different support modes.

PROPAGATION_REQUIRES_NEW initiates a new "internal" transaction that does not depend on the environment. This transaction will be completely commited or rolled back without relying on external transactions. It has its own isolation scope, its own locks, and so on. When internal transactions begin to execute, external transactions will be suspended, and when internal transactions end, external transactions will continue to execute.

On the other hand, PROPAGATION_NESTED starts a "nested" transaction, which is a true sub-transaction of an existing transaction. When a nested transaction starts to execute, it will get a savepoint. If the nested transaction fails, we will roll back to the savepoint. The nested transaction is part of the external transaction and will be submitted only after the external transaction has finished.

Thus, the biggest difference between PROPAGATION_REQUIRES_NEW and PROPAGATION_NESTED is that PROPAGATION_REQUIRES_NEW is a completely new transaction, while PROPAGATION_NESTED is a sub-transaction of external transaction. If external transaction commit, nested transaction will be commit, which is also applicable to roll back.

PROPAGATION_REQUIRED should be our first business communication behavior. It can meet most of our business needs.

2.2.2 isolation level

The second dimension of a transaction is the isolation level. The isolation level defines the extent to which a transaction may be affected by other concurrent transactions.  
(1) Problems caused by concurrent transactions
In typical applications, multiple transactions run concurrently, often operating the same data to complete their respective tasks. Concurrency is necessary, but it may cause some problems.

  • Dirty reads - Dirty reads occur when one transaction reads data rewritten by another transaction that has not yet been submitted. If the rewrite is rolled back later, the data obtained by the first transaction is invalid.
  • Non-repeatable read -- Non-repeatable read occurs when a transaction performs the same query twice or more, but each time it receives different data. This is usually because another concurrent transaction is updated during two queries.
  • Phantom read - Phantom read is similar to unrepeatable read. It occurs when a transaction (T1) reads several rows of data and then another concurrent transaction (T2) inserts some data. In subsequent queries, the first transaction (T1) will find some additional records that did not exist.

The Difference between Unrepeatable Reading and Illusive Reading

The emphasis of non-repeatable reading is modification:
Under the same conditions, you read the data and read it again to find that the value is different.
For example, in transaction 1, Mary read her salary of 1000 and the operation was not completed.

    con1 = getConnection();  
    select salary from employee empId ="Mary";  

In transaction 2, the financial officer modified Mary's salary to 2000 and submitted the transaction.

    con2 = getConnection();  
    update employee set salary = 2000;  
    con2.commit();  

In transaction 1, when Mary reads her salary again, the salary changes to 2000.

    //con1  
    select salary from employee empId ="Mary"; 

The results of two reads in a transaction are inconsistent, resulting in non-repeatable reads.

The emphasis of hallucination is to add or delete:
Under the same conditions, the number of records read out for the first and second time is different.
For example, there are currently 10 employees with a salary of 1000. Transaction 1, read all employees whose salary is 1000.

    con1 = getConnection();  
    Select * from employee where salary =1000; 

A total of 10 records were read

Then another transaction inserts an employee record into the employee table, and the salary is 1000.

    con2 = getConnection();  
    Insert into employee(empId,salary) values("Lili",1000);  
    con2.commit();  

Transaction 1 reads all employees whose salary is 1000 again

    //con1  
    select * from employee where salary =1000;  

A total of 11 records were read, which resulted in phantom reading.

From the overall results, it seems that both unrepeatable reading and hallucination are inconsistent. But if you look at it from a control point of view, there's a big difference between the two.  
For the former, only the records satisfying the conditions need to be locked.  
For the latter, the records satisfying the conditions and their similarities should be locked.

(2) isolation level

Isolation level Meaning
ISOLATION_DEFAULT Use the default isolation level of the back-end database
ISOLATION_READ_UNCOMMITTED Minimum isolation level, allowing access to uncommitted data changes, may result in dirty, hallucinatory, or unrepeatable reads
ISOLATION_READ_COMMITTED Allowing access to data submitted by concurrent transactions prevents dirty reading, but hallucination or non-repeatable reading may still occur.
ISOLATION_REPEATABLE_READ The results of multiple reads of the same field are consistent. Unless the data is modified by the transaction itself, dirty and non-repeatable reads can be prevented, but hallucination may still occur.
ISOLATION_SERIALIZABLE The highest isolation level, fully subject to the isolation level of ACID, ensures that dirty reading, unrepeatable reading and hallucination are prevented, and the slowest transaction isolation level, because it is usually achieved by completely locking transaction-related database tables.

2.2.3 read-only

The third feature of a transaction is whether it is a read-only transaction. If the transaction only operates on the back-end database, the database can take advantage of the read-only nature of the transaction to perform some specific optimizations. By setting the transaction to read-only, you can give the database an opportunity to apply the optimizations it deems appropriate.

2.2.4 Transaction timeout

In order to make the application run well, transactions cannot run for too long. Because transactions may involve locking back-end databases, long-term transactions will unnecessarily occupy database resources. Transaction timeout is a timer of a transaction. If the transaction is not completed within a certain time, it will automatically roll back instead of waiting for its end.

2.2.5 rollback rule

The last aspect of the transaction Pentagon is a set of rules that define which exceptions cause the transaction to roll back and which do not. By default, transactions roll back only when they encounter run-time exceptions, and they do not roll back when they encounter checked exceptions (this behavior is consistent with the rollback behavior of EJB)
But you can declare that a transaction rolls back when it encounters a specific checked exception as if it encounters a runtime exception. Similarly, you can declare that transactions do not roll back when they encounter specific exceptions, even if they are run-time exceptions.

2.3 Transaction Status

The method of calling getTransaction() of the Platform Transaction Manager interface mentioned above yields an implementation of the Transaction Status interface, which is as follows:

public interface TransactionStatus{
    boolean isNewTransaction(); // Is it something new?
    boolean hasSavepoint(); // Is there a recovery point?
    void setRollbackOnly();  // Set to rollback only
    boolean isRollbackOnly(); // Is it rollback only?
    boolean isCompleted; // Has it been completed?
} 

It can be found that this interface describes some simple methods for transaction processing to control transaction execution and query transaction status, and the corresponding transaction status needs to be applied when rolling back or submitting.

3 Programming Transactions

3.1 Differences between Programming and Declarative Transactions

Spring provides support for programmatic and declarative transactions, which allow users to define transaction boundaries precisely in code, and declarative transactions (based on AOP) help users decouple operations from transaction rules.  
Simply put, programmable transactions intrude into business code, but provide more detailed transaction management; and declarative transactions can not only play a role of transaction management, but also do not affect the specific implementation of business code because of AOP.

3.2 How to implement programmable transactions?

Spring provides two ways of programmatic transaction management: using Transaction Template and using Platform Transaction Manager directly.

3.2.1 Use Transaction Template

Using Transaction Template and other Spring templates, such as JdbcTempalte and Hibernate Template, are the same approach. It uses a callback method to free the application from processing to fetch and release resources. Like other templates, Transaction Template is thread-safe. Code snippet:

    TransactionTemplate tt = new TransactionTemplate(); // Create a new Transaction Template
    Object result = tt.execute(
        new TransactionCallback(){  
            public Object doTransaction(TransactionStatus status){  
                updateOperation();  
                return resultOfUpdateOperation();  
            }  
    }); // Execute the execute method for transaction management

A value can be returned using TransactionCallback(). If you use Transaction Callback Without Result, there is no return value.

3.2.2 Use Platform Transaction Manager

The sample code is as follows:


    DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); //Define a Transaction Manager for a framework platform, such as JDBC, Hibernate
    dataSourceTransactionManager.setDataSource(this.getJdbcTemplate().getDataSource()); // set up data sources
    DefaultTransactionDefinition transDef = new DefaultTransactionDefinition(); // Define transaction attributes
    transDef.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED); // Setting propagation behavior properties
    TransactionStatus status = dataSourceTransactionManager.getTransaction(transDef); // Obtain transaction status
    try {
        // Database operation
        dataSourceTransactionManager.commit(status);// Submission
    } catch (Exception e) {
        dataSourceTransactionManager.rollback(status);// RollBACK
    }

4 Declarative Transactions

4.1 Configuration

Note: The following configuration codes are referenced from Five ways to configure Spring transactions

According to the different proxy mechanisms, five configurations of Spring transactions are summarized. The configuration files are as follows:

(1) Each Bean has a proxy

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

    <bean id="sessionFactory" 
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
        <property name="configLocation" value="classpath:hibernate.cfg.xml" /> 
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean> 

    <!-- Define the transaction manager (declarative transactions) --> 
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    <!-- To configure DAO -->
    <bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    <bean id="userDao" 
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> 
           <!-- Configuring Transaction Manager --> 
           <property name="transactionManager" ref="transactionManager" />    
        <property name="target" ref="userDaoTarget" /> 
         <property name="proxyInterfaces" value="com.bluesky.spring.dao.GeneratorDao" />
        <!-- Configuring Transaction Properties --> 
        <property name="transactionAttributes"> 
            <props> 
                <prop key="*">PROPAGATION_REQUIRED</prop>
            </props> 
        </property> 
    </bean> 
</beans>

(2) All beans share a proxy base class

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

    <bean id="sessionFactory" 
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
        <property name="configLocation" value="classpath:hibernate.cfg.xml" /> 
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean> 

    <!-- Define the transaction manager (declarative transactions) --> 
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    <bean id="transactionBase" 
            class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" 
            lazy-init="true" abstract="true"> 
        <!-- Configuring Transaction Manager --> 
        <property name="transactionManager" ref="transactionManager" /> 
        <!-- Configuring Transaction Properties --> 
        <property name="transactionAttributes"> 
            <props> 
                <prop key="*">PROPAGATION_REQUIRED</prop> 
            </props> 
        </property> 
    </bean>   

    <!-- To configure DAO -->
    <bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    <bean id="userDao" parent="transactionBase" > 
        <property name="target" ref="userDaoTarget" />  
    </bean>
</beans>

(3) Use of interceptors

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

    <bean id="sessionFactory" 
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
        <property name="configLocation" value="classpath:hibernate.cfg.xml" /> 
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean> 

    <!-- Define the transaction manager (declarative transactions) --> 
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean> 

    <bean id="transactionInterceptor" 
        class="org.springframework.transaction.interceptor.TransactionInterceptor"> 
        <property name="transactionManager" ref="transactionManager" /> 
        <!-- Configuring Transaction Properties --> 
        <property name="transactionAttributes"> 
            <props> 
                <prop key="*">PROPAGATION_REQUIRED</prop> 
            </props> 
        </property> 
    </bean>

    <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> 
        <property name="beanNames"> 
            <list> 
                <value>*Dao</value>
            </list> 
        </property> 
        <property name="interceptorNames"> 
            <list> 
                <value>transactionInterceptor</value> 
            </list> 
        </property> 
    </bean> 

    <!-- To configure DAO -->
    <bean id="userDao" class="com.bluesky.spring.dao.UserDaoImpl">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
</beans>

(4) Interceptors configured with tx Tags

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

    <context:annotation-config />
    <context:component-scan base-package="com.bluesky" />

    <bean id="sessionFactory" 
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
        <property name="configLocation" value="classpath:hibernate.cfg.xml" /> 
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean> 

    <!-- Define the transaction manager (declarative transactions) --> 
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*" propagation="REQUIRED" />
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <aop:pointcut id="interceptorPointCuts"
            expression="execution(* com.bluesky.spring.dao.*.*(..))" />
        <aop:advisor advice-ref="txAdvice"
            pointcut-ref="interceptorPointCuts" />       
    </aop:config>     
</beans>

(5) Full annotations

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

    <context:annotation-config />
    <context:component-scan base-package="com.bluesky" />

    <tx:annotation-driven transaction-manager="transactionManager"/>

    <bean id="sessionFactory" 
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
        <property name="configLocation" value="classpath:hibernate.cfg.xml" /> 
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean> 

    <!-- Define the transaction manager (declarative transactions) --> 
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

</beans>

At this point, add the @Transactional annotation to the DAO, as follows:

package com.bluesky.spring.dao;

import java.util.List;

import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.stereotype.Component;

import com.bluesky.spring.domain.User;

@Transactional
@Component("userDao")
public class UserDaoImpl extends HibernateDaoSupport implements UserDao {

    public List<User> listUsers() {
        return this.getSession().createQuery("from User").list();
    }  
}

4.2 An example of declarative transactions

First is the database table.
book(isbn, book_name, price) 
account(username, balance) 
book_stock(isbn, stock)

Then comes the XML configuration

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

    <import resource="applicationContext-db.xml" />

    <context:component-scan
        base-package="com.springinaction.transaction">
    </context:component-scan>

    <tx:annotation-driven transaction-manager="txManager"/>

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

</beans>

Classes used
BookShopDao

package com.springinaction.transaction;

public interface BookShopDao {
    // Getting the unit price of a book by its number
    public int findBookPriceByIsbn(String isbn);
    // Update the stock of books so that the number of books corresponds to the stock-1
    public void updateBookStock(String isbn);
    // Update user's account balance: balance-price of account
    public void updateUserAccount(String username, int price);
}

BookShopDaoImpl

package com.springinaction.transaction;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository("bookShopDao")
public class BookShopDaoImpl implements BookShopDao {

    @Autowired
    private JdbcTemplate JdbcTemplate;

    @Override
    public int findBookPriceByIsbn(String isbn) {
        String sql = "SELECT price FROM book WHERE isbn = ?";

        return JdbcTemplate.queryForObject(sql, Integer.class, isbn);
    }

    @Override
    public void updateBookStock(String isbn) {
        //Check whether the inventory of the book is sufficient, if not, throw an exception
        String sql2 = "SELECT stock FROM book_stock WHERE isbn = ?";
        int stock = JdbcTemplate.queryForObject(sql2, Integer.class, isbn);
        if (stock == 0) {
            throw new BookStockException("Insufficient stock!");
        }
        String sql = "UPDATE book_stock SET stock = stock - 1 WHERE isbn = ?";
        JdbcTemplate.update(sql, isbn);
    }

    @Override
    public void updateUserAccount(String username, int price) {
        //Check if the balance is insufficient, if not, throw an exception
        String sql2 = "SELECT balance FROM account WHERE username = ?";
        int balance = JdbcTemplate.queryForObject(sql2, Integer.class, username);
        if (balance < price) {
            throw new UserAccountException("Sorry, your credit is running low!");
        }       
        String sql = "UPDATE account SET balance = balance - ? WHERE username = ?";
        JdbcTemplate.update(sql, price, username);
    }

}

BookShopService

package com.springinaction.transaction;
public interface BookShopService {
     public void purchase(String username, String isbn);
}

BookShopServiceImpl

package com.springinaction.transaction;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {

    @Autowired
    private BookShopDao bookShopDao;

    /**
     * 1.Add transaction annotations
     * Use propagation to specify the propagation behavior of a transaction, that is, how to use a transaction when the current transaction method is invoked by another transaction method.
     * The default value is REQUIRED, even for transactions that invoke methods
     * REQUIRES_NEW: With its own transaction, the transaction of the invoked transaction method is suspended.
     *
     * 2.Use isolation to specify the isolation level of a transaction, the most commonly used value being READ_COMMITTED
     * 3.By default, Spring's declarative transactions roll back all runtime exceptions, or they can be set by the corresponding properties. Normally, the default value is sufficient.
     * 4.Use readOnly to specify whether a transaction is read-only. Represents that this transaction only reads data but does not update it, which helps the database engine optimize transactions. If it's really a worthwhile way to read only the database, you should set readOnly=true
     * 5.Use timeOut to specify the time that transactions can take before a forced rollback.
     */
    @Transactional(propagation=Propagation.REQUIRES_NEW,
            isolation=Isolation.READ_COMMITTED,
            noRollbackFor={UserAccountException.class},
            readOnly=true, timeout=3)
    @Override
    public void purchase(String username, String isbn) {
        //1. Unit price of book acquisition
        int price = bookShopDao.findBookPriceByIsbn(isbn);
        //2. Inventory of Updated Books
        bookShopDao.updateBookStock(isbn);
        //3. Update user balance
        bookShopDao.updateUserAccount(username, price);
    }
}

Cashier

package com.springinaction.transaction;
import java.util.List;
public interface Cashier {
    public void checkout(String username, List<String>isbns);
}

CashierImpl: CashierImpl.checkout and bookShopService.purchase jointly tested transaction propagation behavior

package com.springinaction.transaction;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service("cashier")
public class CashierImpl implements Cashier {
    @Autowired
    private BookShopService bookShopService;

    @Transactional
    @Override
    public void checkout(String username, List<String> isbns) {
        for(String isbn : isbns) {
            bookShopService.purchase(username, isbn);
        }
    }
}

BookStockException


package com.springinaction.transaction;
public class BookStockException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    public BookStockException() {
        super();
        // TODO Auto-generated constructor stub
    }

    public BookStockException(String arg0, Throwable arg1, boolean arg2,
            boolean arg3) {
        super(arg0, arg1, arg2, arg3);
        // TODO Auto-generated constructor stub
    }

    public BookStockException(String arg0, Throwable arg1) {
        super(arg0, arg1);
        // TODO Auto-generated constructor stub
    }

    public BookStockException(String arg0) {
        super(arg0);
        // TODO Auto-generated constructor stub
    }

    public BookStockException(Throwable arg0) {
        super(arg0);
        // TODO Auto-generated constructor stub
    }
}

UserAccountException

package com.springinaction.transaction;
public class UserAccountException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    public UserAccountException() {
        super();
        // TODO Auto-generated constructor stub
    }

    public UserAccountException(String arg0, Throwable arg1, boolean arg2,
            boolean arg3) {
        super(arg0, arg1, arg2, arg3);
        // TODO Auto-generated constructor stub
    }

    public UserAccountException(String arg0, Throwable arg1) {
        super(arg0, arg1);
        // TODO Auto-generated constructor stub
    }

    public UserAccountException(String arg0) {
        super(arg0);
        // TODO Auto-generated constructor stub
    }

    public UserAccountException(Throwable arg0) {
        super(arg0);
        // TODO Auto-generated constructor stub
    }
}

Test class

package com.springinaction.transaction;

import java.util.Arrays;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringTransitionTest {

    private ApplicationContext ctx = null;
    private BookShopDao bookShopDao = null;
    private BookShopService bookShopService = null;
    private Cashier cashier = null;
    {
        ctx = new ClassPathXmlApplicationContext("config/transaction.xml");
        bookShopDao = ctx.getBean(BookShopDao.class);
        bookShopService = ctx.getBean(BookShopService.class);
        cashier = ctx.getBean(Cashier.class);
    }

    @Test
    public void testBookShopDaoFindPriceByIsbn() {
        System.out.println(bookShopDao.findBookPriceByIsbn("1001"));
    }

    @Test
    public void testBookShopDaoUpdateBookStock(){
        bookShopDao.updateBookStock("1001");
    }

    @Test
    public void testBookShopDaoUpdateUserAccount(){
        bookShopDao.updateUserAccount("AA", 100);
    }
    @Test
    public void testBookShopService(){
        bookShopService.purchase("AA", "1001");
    }

    @Test
    public void testTransactionPropagation(){
        cashier.checkout("AA", Arrays.asList("1001", "1002"));
    }
}

Topics: Spring Hibernate Attribute xml