An example of transaction management in Spring

  • 2020-04-01 03:34:24
  • OfStack

This article provides an example of transaction management in Spring. Share with you for your reference. Specific analysis is as follows:

Transaction profile:

Transaction management is an essential technique in enterprise application development to ensure data integrity and consistency

A transaction is a series of actions that are treated as a single unit of work. Either all of these actions are completed or none of them work

Four key properties of a transaction (ACID)

Atomicity: 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 all transactions are completed, the transaction is committed. The data and resources are in a consistent state that satisfies the business rules
Isolation: there may be many transactions that process the same data at the same time, so everything 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. Typically, the results of a transaction are written to the persistent store

Transaction management in Spring

As an enterprise application framework, Spring defines an abstraction layer on top of different transaction management apis. Application developers can use Spring's transaction management mechanisms without having to understand the underlying transaction management API.

Spring supports both programmatic and declarative transaction management

Programmatic transaction management: transaction management code is embedded in business methods to control the commit and rollback of transactions. In programmatic transactions, additional transaction management code must be included in each business operation

Declarative transaction management: better than programmatic transaction management in most cases. It separates transaction management code from business methods and implements transaction management declaratively. Transaction management, as a crosscutting concern, can be modularized through an AOP approach. Spring supports declarative transaction management through the Spring AOP framework.

Propagation properties of Spring transactions:

When a transactional method is invoked by another transactional method, you must specify how the transaction should propagate. For example, a method may continue to run in an existing transaction, or it may start a new transaction and run in its own transaction.

The propagation behavior of a transaction can be specified by the propagation attribute. Spring defines seven propagation behaviors:

Spring Supported transaction propagation behavior
Propagation behavior meaning
PROPAGATION_MANDATORY Means that the method must run in a transaction, and if the current transaction does not exist, an exception is thrown
PROPAGATION_NESTED Means that if a transaction already exists, the method will run in a nested transaction. Nested transactions can be committed or rolled back independently of the current transaction. If the current transaction does not exist, its behavior is associated with PROPAGATION_REQUIRED The same. Note that support for this propagation behavior varies from vendor to vendor. You can refer to the documentation for resource managers to confirm that they support nested transactions
PROPAGATION_NEVER   Indicates that the current method should not run in a transaction context. If a transaction is currently running, an exception is thrown
PROPAGATION_NOT_SUPPORTED Indicates that the method should not run in a transaction. If a current transaction exists, the current transaction is suspended while the method is running. If you are using JTATransactionManager Access is required TransactionManager
PROPAGATION_REQUIRED Indicates 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 is started
PROPAGATION_REQUIRED_NEW Indicates that the current method must run in its own transaction. A new transaction will be started. If a current transaction exists, it is suspended during the execution of the method. If you are using JTATransactionManager Access is required TransactionManager
PROPAGATION_SUPPORTS Represents that the current method does not require a transaction context, but if the current transaction exists, the method will run in that transaction

PROPAGATION_REQUIRED is the default propagation attribute

Problems caused by concurrent transactions

Many unexpected problems can arise when multiple transactions in the same application or in different applications execute concurrently on the same dataset.

Problems caused by concurrent transactions can be divided into the following three categories:

Dirty reading: dirty reading occurs when a transaction reads data overwritten by another transaction but not yet committed. If the overwrite is rolled back later, the data obtained by the first transaction is invalid.

(2) unrepeatable reads: unrepeatable reads occur when a transaction executes the same query twice or more, but each time gets different data. This is usually because another concurrent transaction updates the data between queries

Phantom reading: phantom reading is similar to unrepeatable reading. It occurs when a transaction (T1) reads a few rows of data, followed by another concurrent transaction (T2) inserts some data. In subsequent queries, the first transaction (T1) finds additional records that would not otherwise exist

Code sample

The first is the database table:

This includes book(isbn, book_name, price), account(username, balance), and book_stock(isbn, stock).

Then there are the classes used:

BookShopDao


package com.yl.spring.tx; public interface BookShopDao {
    //Get the unit price of the book according to the isbn
    public int findBookPriceByIsbn(String isbn);
    //Update the book inventory so that the isbn corresponding to the inventory -1
    public void updateBookStock(String isbn);
    //Update the user's account balance: make the balcance-price
for username     public void updateUserAccount(String username, int price);
   
}

BookShopDaoImpl


package com.yl.spring.tx; 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 that the books are in sufficient stock, and 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(" Short 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, and if so, 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(" Insufficient balance! ");
        }       
        String sql = "UPDATE account SET balance = balance - ? WHERE username = ?";
        JdbcTemplate.update(sql, price, username);
    } }

BookShopService

package com.yl.spring.tx;
public interface BookShopService {
     public void purchase(String username, String isbn);
}

BookShopServiceImpl


package com.yl.spring.tx; 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 Specifies the propagation behavior of a transaction, that is, how a transaction is used when the current transaction method is invoked by another transaction method.
     * The default value is REQUIRED , that is, the transaction that USES the calling method
     * REQUIRES_NEW : with its own transaction, the transaction of the invoked transaction method is suspended.
     *
     * 2. use isolation Specifies the isolation level of the transaction, most commonly evaluated as READ_COMMITTED
     * 3. By default Spring All runtime exceptions are rolled back by the declarative transactions of, or can be set by the corresponding properties. In general, the default is fine.
     * 4. use readOnly Specifies whether the transaction is read-only. Means that the transaction only reads data but does not update it, which helps the database engine optimize the transaction. If it is really a read only database worthy method, it should be set readOnly=true
     * 5. use timeOut Specifies the amount of time that a transaction can take up before it is forced to roll back.
     */
    @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. Get the unit price of the book
        int price = bookShopDao.findBookPriceByIsbn(isbn);
        //2. Update book inventory
        bookShopDao.updateBookStock(isbn);
        //3. Update user balance
        bookShopDao.updateUserAccount(username, price);;
    }
}

BookStockException


package com.yl.spring.tx;
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.yl.spring.tx;
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
    }
}

Cashier


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

CashierImpl. Cashierimpl.checkout and bookshopservice.purchase jointly tested the transaction propagation behavior


package com.yl.spring.tx; 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);
        }
    }
}

The test class:


package com.yl.spring.tx; 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("applicationContext.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"));
    }
}

I hope this article has helped you with your Spring programming.


Related articles: