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);
    }

前程有光
936 声望618 粉丝