This article describes transaction management in Spring with examples. Share it with everyone for your reference. The specific analysis is as follows:
Business introduction:
Transaction management is an essential technology 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 work, or none of them work
Four key attributes of transactions (ACID)
① Atomicity: An atomic operation in the transaction room consists of a series of actions. The atomicity of transactions ensures that actions are either fully completed or have no effect at all ② Consistency: Once all transaction actions are completed, the transaction is committed. Data and resources are in a consistent state that meets business rules ③ Isolation: There may be many transactions processing the same data at the same time, so each thing should be isolated from other transactions to prevent data corruption ④ Durability: Once a transaction is completed, its results should not be affected no matter what system errors occur. Typically, the results of a transaction are written to persistent storage
Transaction management in Spring
As an enterprise application framework, Spring defines an abstraction layer on top of different transaction management APIs. Application developers do not need to understand the underlying transaction management API to use Spring's transaction management mechanism.
Spring supports both programmatic transaction management and declarative transaction management.
Programmatic transaction management: Embed transaction management code into business methods to control transaction submission and rollback. In programmatic transactions, additional transaction management code must be included in each business operation.
Declarative transaction management: Better to use than programmatic transaction management in most cases. It separates transaction management code from business methods and implements transaction management in a declarative manner. Transaction management, as a cross-cutting concern, can be modularized through AOP methods. Spring supports declarative transaction management through the Spring AOP framework.
Propagation properties of Spring transactions:
When a transaction method is called by another transaction method, you must specify how the transaction should be propagated. For example: the method may continue to run in the 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 7 communication behaviors:
communication behavior | meaning |
PROPAGATION_MANDATORY | Indicates that the method must be run within a transaction. If the current transaction does not exist, an exception will be thrown. |
PROPAGATION_NESTED | Indicates that if a transaction currently exists, this method will be 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, the behavior is the same as PROPAGATION_REQUIRED. Note that each manufacturer's support for this propagation behavior is different. You can consult your resource managers' documentation to confirm whether they support nested transactions. |
PROPAGATION_NEVER | Indicates that the current method should not be run in a transaction context. If there is currently a transaction running, an exception will be thrown |
PROPAGATION_NOT_SUPPORTED | Indicates that this method should not be run within a transaction. If a current transaction exists, it will be suspended while this method is running. If you use JTATransactionManager, you need to access TransactionManager |
PROPAGATION_REQUIRED | Indicates that the current method must be run within a transaction. If the current transaction exists, the method will be run in that transaction. Otherwise, a new transaction will be 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 will be suspended during the execution of this method. If you use JTATransactionManager, you need to access TransactionManager |
PROPAGATION_SUPPORTS | Indicates that the current method does not require a transaction context, but if there is a current transaction, the method will run in this transaction |
Among them 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 data set.
Problems caused by concurrent transactions can be divided into the following three categories:
① Dirty read: Dirty read occurs when one transaction reads data that has been rewritten by another transaction but has not yet been committed. If the rewrite is later rolled back, the data obtained by the first transaction will be invalid.
② Non-repeatable read: Non-repeatable read occurs when a transaction executes the same query twice or more, but gets different data each time. This is usually because another concurrent transaction updated the data between queries
③ Phantom reading: Phantom reading is similar to non-repeatable reading. It happens when one transaction (T1) reads a few rows of data, and then another concurrent transaction (T2) inserts some data. In subsequent queries, the first transaction (T1) will find that there are some more records that did not exist originally.
code example
First is the database table:
Including book(isbn, book_name, price), account(username, balance), book_stock(isbn, stock)
Then the classes used:
BookShopDao
Copy the code code as follows:
package com.yl.spring.tx;
public interface BookShopDao {
//Get the unit price of the book based on the book number
public int findBookPriceByIsbn(String isbn);
//Update the inventory of the book so that the inventory corresponding to the book number is -1
public void updateBookStock(String isbn);
//Update the user's account balance: make username's balance-price
public void updateUserAccount(String username, int price);
}
BookShopDaoImpl
Copy the code code as follows:
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 whether the book inventory 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 whether 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("Insufficient balance!");
}
String sql = "UPDATE account SET balance = balance - ? WHERE username = ?";
JdbcTemplate.update(sql, price, username);
}
}
BookShopService
Copy the code as follows: package com.yl.spring.tx;
public interface BookShopService {
public void purchase(String username, String isbn);
}
BookShopServiceImpl
Copy the code code as follows:
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 to specify the propagation behavior of the transaction, that is, how to use the transaction when the current transaction method is called by another transaction method.
* The default value is REQUIRED, which means using the transaction calling the method
* REQUIRES_NEW: Use your own transaction, and the transaction of the called transaction method is suspended.
*
* 2. Use isolation to specify the isolation level of the transaction. The most commonly used value is READ_COMMITTED.
* 3. By default, Spring's declarative transaction rolls back all runtime exceptions, which can also be set through the corresponding properties. Normally, the default value is sufficient.
* 4. Use readOnly to specify whether the transaction is read-only. Indicates that this transaction only reads data but does not update data, which can help the database engine optimize the transaction. If it is really a method that only reads the database, readOnly=true should be set.
* 5. Use timeOut to specify the time that a transaction can take before forcing a 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. 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
Copy the code code as follows:
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
Copy the code code as follows:
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
Copy the code code as follows:
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 test the transaction propagation behavior
Copy the code code as follows:
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);
}
}
}
Test class:
Copy the code code as follows:
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 will be helpful to everyone’s Spring programming design.