Spread of spring transactions
The spread of the transaction
Research is the processing strategy when multiple transactions exist
1) REQUIRED: If there is a transaction, the current transaction is supported, and if there is no current transaction, a new transaction is created. This is the most common choice.
2) SUPPORTS: If there is a transaction, the current transaction is supported, and if there is no current transaction, it is executed in a non-transactional manner.
3) MANDATORY: If there is a transaction, the current transaction is supported, and if there is no current transaction, an exception is thrown.
4) REQUIRES_NEW: Create a new transaction, if there is a transaction currently, suspend the current transaction.
5) NOT_SUPPORTED: The operation is performed in a non-transactional manner. If there is a transaction currently, the current transaction is suspended.
6) NEVER: Execute in a non-transactional manner. If there is currently a transaction, an exception will be thrown.
7) NESTED: Support the current transaction, add Savepoint points, and submit or roll back synchronously with the current transaction.
Personally organize some information, friends in need can click to receive it directly.
[Java Basic Knowledge ]( 1609bdf6bad9ac https://docs.qq.com/doc/DTW9NcEJPS1NlbU50
)
[22 core Java architect books]( https://docs.qq.com/doc/DTW9NcEJPS1NlbU50
)
[From 0 to 1 Java learning route and materials] ( https://docs.qq.com/doc/DTW9NcEJPS1NlbU50
)
questions in 2021]( 1609bdf6bada38 https://docs.qq.com/doc/DTW9NcEJPS1NlbU50
)
Preparation before the test
Prepare the database table
Database transaction_propagation
Account table account, book table book, inventory table book_stock
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for account
-- ----------------------------
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`user_id` int(11) NOT NULL AUTO_INCREMENT,
`user_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`balance` double(11, 2) UNSIGNED NULL DEFAULT NULL,
PRIMARY KEY (`user_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic STORAGE MEMORY;
-- ----------------------------
-- Records of account
-- ----------------------------
INSERT INTO `account` VALUES (1, 'Albert', 100.00);
-- ----------------------------
-- Table structure for book
-- ----------------------------
DROP TABLE IF EXISTS `book`;
CREATE TABLE `book` (
`book_id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`price` double(11, 2) UNSIGNED NULL DEFAULT NULL,
PRIMARY KEY (`book_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1003 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of book
-- ----------------------------
INSERT INTO `book` VALUES (1001, '基础数据结构', 60.00);
INSERT INTO `book` VALUES (1002, '数据库设计', 50.00);
-- ----------------------------
-- Table structure for book_stock
-- ----------------------------
DROP TABLE IF EXISTS `book_stock`;
CREATE TABLE `book_stock` (
`book_id` int(11) NOT NULL AUTO_INCREMENT,
`stock` int(11) UNSIGNED NULL DEFAULT NULL,
PRIMARY KEY (`book_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1003 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of book_stock
-- ----------------------------
INSERT INTO `book_stock` VALUES (1001, 100);
INSERT INTO `book_stock` VALUES (1002, 100);
SET FOREIGN_KEY_CHECKS = 1;
Initialize the spring project
Import some basic dependency packages: jdbc, mysql driver package, test module;
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
Database connection information configuration
#jdbc
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost/transaction_propagation?useUnicode=true&characterEncoding=utf-8
username: root
password: 123456
Service、Dao
Only test calls are used here, and layers such as controller and entity are omitted;
1. First class Dao, write a simple purchase operation: query unit price, update inventory, update account balance;
@Repository
public class BookShopDao {
@Autowired
private JdbcTemplate jdbcTemplate = new JdbcTemplate();
public double getPriceById(Integer bookId) {
String sql = "SELECT price FROM BOOK WHERE book_id = ?";
double price = jdbcTemplate.query(sql, new PreparedStatementSetter() {
@Override
public void setValues(PreparedStatement preparedStatement) throws SQLException {
preparedStatement.setInt(1, bookId);
}
}, new ResultSetExtractor<Double>() {
@Override
public Double extractData(ResultSet resultSet) throws SQLException, DataAccessException {
double p = 0.0;
while (resultSet.next()) {
p = resultSet.getDouble("price");
}
return p;
}
});
return price;
}
public void updateBookStock(Integer bookId, int num) {
String sql = "UPDATE book_stock SET stock = stock - ? WHERE book_id = ?";
jdbcTemplate.update(sql, new PreparedStatementSetter() {
@Override
public void setValues(PreparedStatement preparedStatement) throws SQLException {
preparedStatement.setInt(1, num);
preparedStatement.setInt(2, bookId);
}
});
}
public void updateBalance(Integer userId, double balance) {
//修改金额
String sql = "UPDATE account SET balance = balance - ? WHERE user_id = ?";
jdbcTemplate.update(sql, new PreparedStatementSetter() {
@Override
public void setValues(PreparedStatement preparedStatement) throws SQLException {
preparedStatement.setDouble(1, balance);
preparedStatement.setInt(2, userId);
}
});
}
}
2. The study of transaction communication is actually to study the application of two or more transactions when nested, so here you need to write two services for nested calls; the interface class is omitted here
@Transactional for purchase in BookShopServiceImpl means that the atomicity of (1, 2, 3) must be guaranteed when a purchase order comes in;
@Service
public class BookShopServiceImpl implements BookShopService {
@Autowired
private BookShopDao bookShopDao;
@Transactional
@Override
public void purchase(Integer userId,Integer bookId,int num){
//1、获取要购买的图书价格
double price = bookShopDao.getPriceById(bookId);
//2、更新图书库存
bookShopDao.updateBookStock(bookId,num);
//3、更新用户余额
bookShopDao.updateBalance(userId,price*num);
}
}
@Transactional in the buy method in CashierServiceImpl refers to the atomicity that must be guaranteed when multiple purchase orders appear in an order;
Because an order may contain the purchase of several commodities.
@Service
public class CashierServiceImpl implements CashierService {
@Autowired
private BookShopService bookShopService;
@Transactional
@Override
public void buy(List<Map<String,Object>> buys){
for (Map map : buys){
//购买
bookShopService.purchase((Integer) map.get("userId"),(Integer)map.get("bookId"),(int)map.get("num"));
}
}
}
Test class
@SpringBootTest
public class TestBuy {
@Autowired
private CashierService cashierService;
@Test
void testBookShop(){
List<Map<String,Object>> list = new ArrayList<>();
Map<String,Object> map = new HashMap<>();
map.put("userId",1);
map.put("bookId",1001);
map.put("num",1);
list.add(map);
map = new HashMap<>();
map.put("userId",1);
map.put("bookId",1002);
map.put("num",1);
list.add(map);
try {
cashierService.buy(list);
}catch (Exception e){
e.printStackTrace();
}
System.out.println("购买成功!");
}
}
Description
The above is the use of spring's default transaction propagation: REQUIRED, purchase uses the same transaction to commit. Then there will be such a problem: there are 100 yuan in the account, and now the order submitted is to buy a 60 yuan "Basic Data Structure" and a 50 yuan "Database Design"; then the total order amount is 110 yuan Obviously, the balance on the account is not enough to purchase. The purchase of the "Basic Data Structure" for 60 yuan in the first transaction is successful, but an exception will be thrown when the "Database Design" for another 50 yuan is submitted. At this time, it will be abnormal and rolled back in the outer transaction of CashierServiceImpl.
Use other communicable
REQUIRES_NEW
Declare in the purchase transaction (propagation = Propagation.REQUIRES_NEW); then every time purchase is called, a new transaction will be opened to submit; then the purchase test result will be carried out at this time: the first book will be successfully purchased, and the second book The purchase failed; the purchase transaction called the second time was rolled back because of the exception.
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void purchase(Integer userId,Integer bookId,int num){
//1、获取要购买的图书价格
double price = bookShopDao.getPriceById(bookId);
//2、更新图书库存
bookShopDao.updateBookStock(bookId,num);
//3、更新用户余额
bookShopDao.updateBalance(userId,price*num);
}
MANDATORY
Will force a transaction to be submitted, otherwise an exception will be thrown, the test result is the same as REQUIRED, the entire order is rolled back.
@Transactional(propagation = Propagation.MANDATORY)
@Override
public void purchase(Integer userId,Integer bookId,int num){
//1、获取要购买的图书价格
double price = bookShopDao.getPriceById(bookId);
//2、更新图书库存
bookShopDao.updateBookStock(bookId,num);
//3、更新用户余额
bookShopDao.updateBalance(userId,price*num);
}
Throw an exception if there is no transaction in the outer layer
No existing transaction found for transaction marked with propagation ‘mandatory’
SUPPORTS
If there is a transaction in the outer layer, it is submitted as a transaction. The test result is the same as REQUIRED, and the entire order is rolled back.
@Transactional(propagation = Propagation.SUPPORTS)
@Override
public void purchase(Integer userId,Integer bookId,int num){
//1、获取要购买的图书价格
double price = bookShopDao.getPriceById(bookId);
//2、更新图书库存
bookShopDao.updateBookStock(bookId,num);
//3、更新用户余额
bookShopDao.updateBalance(userId,price*num);
}
If there is no transaction in the outer layer, it will not be submitted as a transaction, and a book will be successfully purchased;
NOT_SUPPORTED
The operation is performed in a non-transactional manner. If there is a transaction currently, the current transaction is suspended. That is, whether there are affairs in the outer layer will not affect the result: a book can be purchased successfully.
@Transactional(propagation = Propagation.NOT_SUPPORTED)
@Override
public void purchase(Integer userId,Integer bookId,int num){
//1、获取要购买的图书价格
double price = bookShopDao.getPriceById(bookId);
//2、更新图书库存
bookShopDao.updateBookStock(bookId,num);
//3、更新用户余额
bookShopDao.updateBalance(userId,price*num);
}
NEVER
It is mandatory that no transaction exists, otherwise an exception will be thrown
@Transactional(propagation = Propagation.NEVER)
@Override
public void purchase(Integer userId,Integer bookId,int num){
//1、获取要购买的图书价格
double price = bookShopDao.getPriceById(bookId);
//2、更新图书库存
bookShopDao.updateBookStock(bookId,num);
//3、更新用户余额
bookShopDao.updateBalance(userId,price*num);
}
An exception is thrown if there is a transaction:
Existing transaction found for transaction marked with propagation ‘never’
NESTED
Support the current transaction, add Savepoint points, and commit or roll back synchronously with the current transaction. The result is the same as REQUIRES, the entire order is rolled back.
@Transactional(propagation = Propagation.NESTED)
@Override
public void purchase(Integer userId,Integer bookId,int num){
//1、获取要购买的图书价格
double price = bookShopDao.getPriceById(bookId);
//2、更新图书库存
bookShopDao.updateBookStock(bookId,num);
//3、更新用户余额
bookShopDao.updateBalance(userId,price*num);
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。