5, spring declarative transaction annotation configuration

1, Transaction overview:

  • A transaction is a series of actions, which are treated as a single unit of work. These actions are either completed or ineffective; For example, users buy books; Before purchasing, you need to confirm ① whether the number of books is sufficient; ② Whether the user account balance is sufficient; If ① the conditions are met, the inventory will be minus - 1; If ② conditions are met   Then account balance - book price; If   ① And ② as long as one of the conditions is not met, the book inventory is rolled back to the previous state (quantity before this operation) and the user balance is rolled back to the original state (balance before this operation); ① If both and ② meet the conditions, the transaction action is completed and the transaction is submitted;
  • Four key attributes of a transaction (ACID)
    • Atomicity: a transaction is an atomic operation consisting of a series of actions. The atomicity of a transaction ensures that the actions are either completely completed or completely ineffective
    • Consistency: once all transaction actions are completed, the transaction is committed. Data and resources are in a consistent state that meets business rules
    • Isolation: many transactions may process the same data at the same time, so everything should be isolated from other transactions to prevent data corruption
    • Persistence: once a transaction is completed, no matter what system error occurs, its result should not be affected. Usually, the result of the transaction is written to the persistence memory
  • Transaction management of spring
    • Support programmatic transaction management: write the transaction management code into the code; Code redundancy exists;
    • Support declarative transaction management: separate the transaction management code from the code and realize transaction management through declaration; Wider and more convenient application;

2, Declarative transaction annotation configuration

The configuration of the transaction is based on the following instance:

Users purchase books; Before purchasing, you need to confirm ① whether the number of books is sufficient; ② Whether the user account balance is sufficient; If ① the conditions are met, the inventory will be minus - 1; If ② conditions are met   Then account balance - book price; If   ① And ② as long as one of the conditions is not met, the book inventory is rolled back to the previous state (the quantity before this operation) and   The user's balance is rolled back to the original state (the balance before this operation); ① If both and ② meet the conditions, the transaction action is completed and the transaction is submitted;

User account table SQL:

1 CREATE TABLE `account` (
2   `id` int(10) NOT NULL AUTO_INCREMENT,
3   `userName` varchar(20) NOT NULL,
4   `balance` varchar(20) NOT NULL,
5   PRIMARY KEY (`id`)
6 ) 

 

Book inventory table bookstockSQL:

1 CREATE TABLE `bookstock` (
2   `id` int(10) NOT NULL AUTO_INCREMENT,
3   `isbn` int(20) NOT NULL,
4   `stock` varchar(20) NOT NULL,
5   PRIMARY KEY (`id`)
6 ) 

 

Book listbooksql:

1 CREATE TABLE `book` (
2   `id` int(10) NOT NULL AUTO_INCREMENT,
3   `Isbn` int(20) NOT NULL,
4   `price` int(10) NOT NULL,
5   `bookName` varchar(20) CHARACTER SET utf8 NOT NULL,
6   PRIMARY KEY (`id`)
7 ) ;

 

1. Configure the transaction manager

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

 

2. Enable transaction annotation

 

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

  The attached xml file introduces the namespace of context tx bean:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4     xmlns:context="http://www.springframework.org/schema/context"
 5     xmlns:tx="http://www.springframework.org/schema/tx"
 6     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
 7         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
 8         http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
 9 
10 <context:component-scan base-package="lixiuming.spring.tx"></context:component-scan>
11 <!-- Import resource file -->
12 <context:property-placeholder location="classpath:db.properties"/>
13 <!-- to configure c3p0 data source -->
14 <bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
15     <property name="user" value="${jdbc.user}"></property>
16     <property name="password" value="${jdbc.password}"></property>
17     <property name="driverClass" value="${jdbc.driverClass}"></property>
18     <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
19     
20     <property name="initialPoolSize" value="${jdbc.initPoolSize}"></property>
21     <property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
22         <property name="maxStatements" value="${jdbc.maxStatements}"></property>
23 </bean>
24 
25 <!-- to configure NamedParameterJdbcTemplate The object can use named parameters. It does not have a parameterless constructor. Constructor parameters must be specified-->
26 <bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
27     <constructor-arg ref="datasource"></constructor-arg>
28 </bean>
29 
30 <!--to configure spring of jdbcTemplate -->
31 <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
32     <property name="dataSource" ref="datasource"></property>
33 </bean>
34 
35 <!-- 1.Configure transaction manager -->
36     <bean id="transactionManager" 
37         class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
38         <property name="dataSource" ref="datasource"></property>
39     </bean>
40     
41     <!-- 2.Enable transaction annotation -->
42     <tx:annotation-driven transaction-manager="transactionManager"/>
43 </beans>
db.properties:
1 jdbc.user=root
2 jdbc.password=
3 jdbc.driverClass=com.mysql.jdbc.Driver
4 jdbc.jdbcUrl=jdbc:mysql:///test
5 
6 
7 jdbc.initPoolSize =5
8 jdbc.maxPoolSize = 10
9 jdbc.maxStatements=0

 

3. Add transaction annotation @ Transactional

DAO layer:

 1 package lixiuming.spring.tx;
 2 
 3 public interface BookShopDao {
 4 
 5     /**
 6      * Find the price of the book according to the book number
 7      * 
 8      * @param Isbn
 9      * @return
10      */
11     public int findBookPriceByIsbn(int Isbn);
12 
13     /**
14      * Reduce the inventory corresponding to the book number by one
15      */
16     public void updateBookSock(int Isbn);
17 
18     /**
19      * Update user's account balance: blank - price
20      */
21     public void updatUserAccount(String userName, int price);
22 
23 }
 1 package lixiuming.spring.tx;
 2 
 3 import org.springframework.beans.factory.annotation.Autowired;
 4 import org.springframework.jdbc.core.JdbcTemplate;
 5 import org.springframework.stereotype.Repository;
 6 
 7 @Repository("bookShopImpl")
 8 public class BookShopImpl implements BookShopDao {
 9 
10     @Autowired
11     private JdbcTemplate jdbcTemplate;
12 
13     @Override
14     public int findBookPriceByIsbn(int Isbn) {
15         String sql = "select price from book where isbn = ? ";
16         return jdbcTemplate.queryForObject(sql, Integer.class, Isbn);
17     }
18 
19     @Override
20     public void updateBookSock(int Isbn) {
21         // Check whether the inventory of books is sufficient. If not, an exception will be thrown
22         String sql2 = "select stock from bookstock where isbn =?";
23         int stock = jdbcTemplate.queryForObject(sql2, Integer.class, Isbn);
24         if (stock == 0) {
25             throw new BookStockException("Insufficient inventory");
26         }
27 
28         String sql = "update bookstock set stock = stock-1 where Isbn = ?";
29         jdbcTemplate.update(sql, Isbn);
30 
31     }
32 
33     @Override
34     public void updatUserAccount(String userName, int price) {
35         String sql2 = "select balance from account where userName =?";
36         int account = jdbcTemplate.queryForObject(sql2, Integer.class, userName);
37         if (account < price) {
38             throw new UserAccountException("Sorry, your credit is running low");
39         }
40         String sql = "update account set balance = balance-? where userName =?";
41         jdbcTemplate.update(sql, price, userName);
42     }
43 
44 }

Service layer:

1 package lixiuming.spring.tx;
2 
3 public interface BookShopService {
4     
5     public void purchase(String userName,int isbn);
6 
7 }
 1 package lixiuming.spring.tx;
 2 
 3 import org.springframework.beans.factory.annotation.Autowired;
 4 import org.springframework.stereotype.Service;
 5 import org.springframework.transaction.annotation.Propagation;
 6 import org.springframework.transaction.annotation.Transactional;
 7 
 8 @Service("bookShopServiceImpl")
 9 public class BookShopServiceImpl implements BookShopService {
10 
11     @Autowired
12     private BookShopDao dao;
13 
14     @Transactional15     @Override
16     public void purchase(String userName, int isbn) {
17         // Unit price of the book
18         int price = dao.findBookPriceByIsbn(isbn);
19         // Update inventory
20         dao.updateBookSock(isbn);
21         // Update balance
22         dao.updatUserAccount(userName, price);
23 
24     }
25 
26 }

Other (custom exception):

  • Abnormal inventory
 1 package lixiuming.spring.tx;
 2 
 3 public class BookStockException extends RuntimeException {
 4 
 5     /**
 6      * 
 7      */
 8     private static final long serialVersionUID = 4237643951857538899L;
 9 
10     /**
11      * 
12      */
13 
14     public BookStockException() {
15         super();
16         // TODO Auto-generated constructor stub
17     }
18 
19     public BookStockException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
20         super(message, cause, enableSuppression, writableStackTrace);
21         // TODO Auto-generated constructor stub
22     }
23 
24     public BookStockException(String message, Throwable cause) {
25         super(message, cause);
26         // TODO Auto-generated constructor stub
27     }
28 
29     public BookStockException(String message) {
30         super(message);
31         // TODO Auto-generated constructor stub
32     }
33 
34     public BookStockException(Throwable cause) {
35         super(cause);
36         // TODO Auto-generated constructor stub
37     }
38 
39 }
  • User account exception
 1 package lixiuming.spring.tx;
 2 
 3 public class UserAccountException extends RuntimeException {
 4 
 5     /**
 6      * 
 7      */
 8     private static final long serialVersionUID = -3973495734669194251L;
 9 
10     /**
11      * 
12      */
13 
14     public UserAccountException() {
15         super();
16         // TODO Auto-generated constructor stub
17     }
18 
19     public UserAccountException(String message, Throwable cause, boolean enableSuppression,
20             boolean writableStackTrace) {
21         super(message, cause, enableSuppression, writableStackTrace);
22         // TODO Auto-generated constructor stub
23     }
24 
25     public UserAccountException(String message, Throwable cause) {
26         super(message, cause);
27         // TODO Auto-generated constructor stub
28     }
29 
30     public UserAccountException(String message) {
31         super(message);
32         // TODO Auto-generated constructor stub
33     }
34 
35     public UserAccountException(Throwable cause) {
36         super(cause);
37         // TODO Auto-generated constructor stub
38     }
39 
40 }

 

Test method:

 1 package lixiuming.spring.tx;
 2 
 3 import org.junit.Test;
 4 import org.springframework.context.ApplicationContext;
 5 import org.springframework.context.support.ClassPathXmlApplicationContext;
 6 
 7 public class SpringTransactionTest {
 8 
 9     private ApplicationContext cxt = null;
10     private BookShopService parchase = null;
11 
12     {
13         cxt = new ClassPathXmlApplicationContext("application_transaction.xml");
14         parchase = cxt.getBean(BookShopService.class);
15     }
16 
17     @Test
18     public void testpurchase() {
19         parchase.purchase("aa", 1001);
20     }
21 
22 }

4. Test

Test premise: the account amount in the user account table is 120; The book inventory of book No. 1001 is 10;

When testpulse is run for the first time, there is no exception; The inventory with book No. 1001 is 9 and the account amount is 20; An exception is thrown when the testpulse is executed for the second time; The exception is insufficient balance and   The inventory with book No. 1001 is 9 and the account amount is 20;

 

3, Transaction propagation behavior of declarative transactions

  • When a transaction method is called by another transaction method, you must specify how the transaction should propagate. 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
  • Use propagation to specify the propagation behavior of the transaction,

  • The propagation behavior of transactions can be specified by the propagation attribute. Spring defines 7   Category propagation behavior. The default is REQUIRED, and commonly used are REQUIRED and REQUIRED_NEW;
  •  

1.REQUIRED

In the previous section, users purchase books. Add another transaction method, checkout, which calls the purchase method to test the propagation behavior of transactions;

add to   Cashier interface and its implementation class:

1 package lixiuming.spring.tx;
2 
3 import java.util.List;
4 
5 public interface Cashier {
6 
7     public void checkout(String username, List<Integer> isbns);
8 
9 }
 1 package lixiuming.spring.tx;
 2 
 3 import java.util.List;
 4 
 5 import org.springframework.beans.factory.annotation.Autowired;
 6 import org.springframework.stereotype.Service;
 7 import org.springframework.transaction.annotation.Transactional;
 8 
 9 @Service("cashierImpl")
10 public class CashierImpl implements Cashier {
11 
12     @Autowired
13     private BookShopService bookShopService;
14 
15     @Transactional
16     @Override
17     public void checkout(String username, List<Integer> isbns) {
18         for (Integer isbn : isbns) {
19             bookShopService.purchase(username, isbn);
20         }
21     }
22 
23 }

Test method:

 1 package lixiuming.spring.tx;
 2 
 3 import java.util.Arrays;
 4 
 5 import org.junit.Test;
 6 import org.springframework.context.ApplicationContext;
 7 import org.springframework.context.support.ClassPathXmlApplicationContext;
 8 
 9 public class SpringTransactionTest {
10 
11     private ApplicationContext cxt = null;
12     private BookShopService parchase = null;
13     private Cashier c = null;
14 
15     {
16         cxt = new ClassPathXmlApplicationContext("application_transaction.xml");
17         parchase = cxt.getBean(BookShopService.class);
18         c = cxt.getBean(Cashier.class);
19     }
20 
21     @Test
22     public void testCheckout() {
23         c.checkout("aa", Arrays.asList(1001, 1002));
24 
25     }
26 
27     @Test
28     public void testpurchase() {
29         parchase.purchase("aa", 1001);
30     }
31 
32 }

 

Test:

Test premise: the account amount in the user account table is 120; The book inventory of book numbers 1001 and 1002 is 10; When buying the first book, the account balance is enough, but the money for the second book is not enough;

When testCheckout is run for the first time, an error is reported as insufficient balance;   Whether the book inventory of book numbers 1001 and 1002 is 10; The account amount in the user account table is 120;

 

REQUIRED_NEW:

Change the purchase method: set the propagation behavior of the transaction to requirements_ New (i.e. @ transactional (propagation = propagation. Requirements_new)

Requirements_new uses its own transaction, and the calling transaction is suspended

 1     @Transactional(propagation = Propagation.REQUIRES_NEW)
 2     @Override
 3     public void purchase(String userName, int isbn) {
 4         // Unit price of the book
 5         int price = dao.findBookPriceByIsbn(isbn);
 6         // Update inventory
 7         dao.updateBookSock(isbn);
 8         // Update balance
 9         dao.updatUserAccount(userName, price);
10 
11     }

Test:

Test premise: the account amount in the user account table is 120; the book inventory of book numbers 1001 and 1002 is 10; when purchasing the first book, the account balance is enough, but the money for the second book is not enough;

When testCheckout is run for the first time, an error is reported as insufficient balance;   The book inventory of book No. 1001 is 9, and the book inventory of book No. 1002 is 10  ; The account amount in the user account table is 20;

 

4, Isolation level of transaction and setting rollback transaction properties

1. Isolation level of transactions

  • Use isolation to specify the isolation level of transactions. The most common is READ_COMMITTED
  • By default, the trial transaction is declared to roll back runtime exceptions, and the corresponding properties can also be set

 

 

2. Rollback transaction attributes (rollbackFor, noRollbackFor)  )

  • rollbackFor:   Must roll back when encountered
  • noRollbackFor: a group of exception classes that must not be rolled back when encountered

Example:

 @Transactional(propagation=Propagation.REQUIRES_NEW ,isolation=Isolation.READ_COMMITTED,,noRollbackFor = {UserAccountException.class})

Generally, it is not set;

 

5, Timeout and read-only properties

  • Timeout transaction attribute: how long the transaction can last before forced rollback. This can prevent long-running transactions from occupying resources
  • Read only transaction attribute: indicates that this transaction only reads data but does not update data, which can help the database engine optimize transactions

Timeout:

Change the purchase method: use timeout=1 to specify the time that the transaction can take before forced rollback, unit: seconds. For example, if the thread is suspended for 5 seconds, it will be forced to exit

 1     @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED,  timeout = 1)
 2     @Override
 3     public void purchase(String userName, int isbn) {
 4         try {
 5             Thread.sleep(5000);
 6         } catch (InterruptedException e) {
 7         }
 8 
 9         // Unit price of the book
10         int price = dao.findBookPriceByIsbn(isbn);
11         // Update inventory
12         dao.updateBookSock(isbn);
13         // Update balance
14         dao.updatUserAccount(userName, price);
15 
16     }

Test:

Test premise: the account amount in the user account table is 120; the book inventory of book numbers 1001 and 1002 is 10; when purchasing the first book, the account balance is enough;

Run the testpurchase method; buy only 1001 books:

Error: org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Sun Nov 07 22:29:18 CST 2021

The account amount in the user account table is 120; The stock of books with book numbers 1001 and 1002 is 10  

read-only

Use readOnly to specify whether the transaction is read-only 'readOnly=false'. If only the database is read, the method readOnly=true:

To change the purchase method:

 1     @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, readOnly = true)
 2     @Override
 3     public void purchase(String userName, int isbn) {
 4         // Unit price of the book
 5         int price = dao.findBookPriceByIsbn(isbn);
 6         // Update inventory
 7         dao.updateBookSock(isbn);
 8         // Update balance
 9         dao.updatUserAccount(userName, price);
10 
11     }

Test:

Test premise: the account amount in the user account table is 120; The book inventory of book numbers 1001 and 1002 is 10; When buying the first book, the account balance is enough;

Run the testpulse method; Buy only 1001 books:

report errors:

org.springframework.dao.TransientDataAccessResourceException: PreparedStatementCallback; SQL [update bookstock set stock = stock-1 where Isbn = ?]; Connection is read-only. Queries leading to data modification are not allowed...

The account amount in the user account table is 120; The stock of books with book numbers 1001 and 1002 is 10  

Tags: Spring

Posted on Sun, 07 Nov 2021 13:15:49 -0500 by hairyjim