Propagation behavior and isolation of spring things

About the @Transactional annotation:

Add transaction annotation
1. Use propagation to specify the propagation behavior of the transaction, that is, when the current transaction method is called by another transaction method
How to use transactions, the default value is REQUIRED, that is, use the transaction that calls the method
REQUIRES_NEW: The transaction’s own transaction, 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. You can also use the corresponding
Properties are set. Normally, just go to the default value.
4. Use readOnly to specify whether the transaction is read-only. This means that the transaction only reads data but does not update data.
This can help the database engine optimize transactions. If it is really a method that only reads database values, readOnly=true should be set
5. Use timeout to specify how long a transaction can take before forcing a rollback.

I have some personal questions. At the end, let’s use tutorial examples.

My code is as follows:

BookShopDao interface

package com.demo.spring.bean;

public interface BookShopDao {

//Return the unit price of the book based on the book number
    public int findBookPriceByIsbn(String isbn);
//Return the lost inventory based on the book number
    public void updateBookStock(String isbn);
//Update user account balance
    public void updateUserAccount(String username, int price);
}

Implementation class

package com.demo.spring.bean;

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) {
        // TODO Auto-generated method stub
        String sql = "SELECT price FROM book WHERE isbn=?";
        Integer price = jdbcTemplate.queryForObject(sql, Integer.class, isbn);
        return price;
    }

    @Override
    public void updateBookStock(String isbn) {
        // TODO Auto-generated method stub
        String sql = "SELECT stock FROM book_stock WHERE isbn= ? ";
        Integer stock = jdbcTemplate.queryForObject(sql, Integer.class, isbn);
        if (stock == 0) {
            throw new BookStockException("Insufficient stock!!");
        }
        String sql1 = "UPDATE book_stock SET stock=stock-1 WHERE isbn=?";
        jdbcTemplate.update(sql1, isbn);
    }

    @Override
    public void updateUserAccount(String username, int price) {
        // TODO Auto-generated method stub
        String sql1 = "SELECT balance FROM account WHERE username= ? ";
        Integer account = jdbcTemplate.queryForObject(sql1, Integer.class,
                username);
        if (account < price) {
            throw new AccountException("Insufficient balance!!");
        }
        String sql = "UPDATE account SET balance=balance-? WHERE username=?";
        jdbcTemplate.update(sql, price, username);
    }

}

BookShopService interface

package com.demo.spring.bean;

public interface BookShopService {
//Buy books
public void purchase(String username, String isbn);
}

Implementation class

package com.demo.spring.bean;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {

    
    @Autowired
    private BookShopDao bookShopDao;
// @Transactional(propagation=Propagation.REQUIRES_NEW,isolation=Isolation.READ_COMMITTED,readOnly=true,timeout=5,noRollbackFor=AccountException.class)
 
    @Transactional
    @Override
    public void purchase(String username, String isbn) {
        
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        // TODO Auto-generated method stub
        int price = bookShopDao.findBookPriceByIsbn(isbn);
        bookShopDao.updateBookStock(isbn);
        bookShopDao.updateUserAccount(username, price);
    }

}

Cashier bulk book purchase interface

package com.demo.spring.bean;

import java.util.List;

public interface Cashier {
// Buy books in bulk
    public void checkout(String username, List<String> isbns);
    
}

Implementation class

package com.demo.spring.bean;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


@Service("cashier")
public class CashierImpl implements Cashier {

    @Autowired
    private BookShopService bookShopService;
// @Transactional
    @Override
    public void checkout(String username, List<String> isbns) {
        // TODO Auto-generated method stub
        for (String isbn : isbns) {
            bookShopService.purchase(username, isbn);
        }

    }

}

Insufficient account balance and insufficient inventory exceptions (customized exceptions)

package com.demo.spring.bean;

public class AccountException extends RuntimeException {

    /**
     *
     */
    private static final long serialVersionUID = 1L;

    public AccountException() {
        super();
        // TODO Auto-generated constructor stub
    }

    public AccountException(String message, Throwable cause,
            boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
        // TODO Auto-generated constructor stub
    }

    public AccountException(String message, Throwable cause) {
        super(message, cause);
        // TODO Auto-generated constructor stub
    }

    public AccountException(String message) {
        super(message);
        // TODO Auto-generated constructor stub
    }

    public AccountException(Throwable cause) {
        super(cause);
        // TODO Auto-generated constructor stub
    }
    

}
package com.demo.spring.bean;

public class BookStockException extends RuntimeException {

    /**
     *
     */
    private static final long serialVersionUID = 1L;

    public BookStockException() {
        super();
        // TODO Auto-generated constructor stub
    }

    public BookStockException(String message, Throwable cause,
            boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
        // TODO Auto-generated constructor stub
    }

    public BookStockException(String message, Throwable cause) {
        super(message, cause);
        // TODO Auto-generated constructor stub
    }

    public BookStockException(String message) {
        super(message);
        // TODO Auto-generated constructor stub
    }

    public BookStockException(Throwable cause) {
        super(cause);
        // TODO Auto-generated constructor stub
    }
    

}

Test class

package com.demo.spring.bean;

import java.util.Arrays;

import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainTest {


    private ApplicationContext ctx;
    @Autowired
    private cashier cashier;
    {
        ctx=new ClassPathXmlApplicationContext("bean.xml");
        cashier=(Cashier) ctx.getBean("cashier");
    }
    
     @Test
     public void test(){
// System.out.println(bookShopDao.findBookPriceByIsbn("1001"));
         cashier.checkout("rongrong", Arrays.asList("1001","1002"));
     }
    
        

}

bean file

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd">
    <!-- Load self-imported packages -->
    <context:component-scan base-package="com.demo.spring.bean"></context:component-scan>
    <!--Introducing external data sources -->
    <context:property-placeholder location="classpath:db.properties"/>
    <!-- Configure mysql data source -->
    <bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${db.driverClassName}"></property>
        <property name="url" value="${db.url}"></property>
        <property name="username" value="${db.username}"></property>
        <property name="password" value="${db.password}"></property>
    </bean>
    <!-- Configure jdbc template -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="datasource"></property>
    </bean>
    <!-- Configure transaction manager -->
    <bean id="transactionManagertest" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="datasource"></property>
    </bean>
    <!-- Enable transaction annotations -->
    <tx:annotation-driven transaction-manager="transactionManagertest"/>

</beans>

data source

db.driverClassName=com.mysql.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/spring
db.username=root
db.password=root

The questions are as follows:

Prerequisite: The user purchases books in the order of 1001, 1002, and calls the checkout interface to purchase books in batches.

The @Transactional annotation does satisfy atomic operations, either do it all or not.
However, I tested it and found that if Transactional is not added above public void checkout(String username, List isbns)
It has the same effect as adding @Transactional(propagation=Propagation.REQUIRES_NEW) above public void purchase(String username, String isbn)
When the account balance is 120, it can be satisfied that the unit price of 1001 is subtracted from the account, and the inventory of 1001 is subtracted by 1
However, if you change the account balance to 80, you can buy the book of 1002 based on the unit price. Buy the book in the order above.
Adding the annotation @Transactional(propagation=Propagation.REQUIRES_NEW as above has the same effect, but it doesn’t work at all.

I personally think that because the account balance is being updated, there is a judgment that the unit price of the book 1001 is indeed greater than the current account balance of 80. The judgment is made first, so the code after the exception is thrown does not go away.

In addition, the current account balance can buy the book 1002. If I want to use the @Transactional annotation without changing the order of buying books, can I buy the book 1002? , subtract the current account balance of 80, and update the inventory of 1002 books. Can any master see it and help me see how to use this annotation to achieve it?

Attached: sql

/*
Navicat MySQL Data Transfer

Source Server: myTestdata
Source Server Version: 50627
Source Host: localhost:3306
Source Database: spring

Target Server Type: MYSQL
Target Server Version: 50627
File Encoding: 65001

Date: 2017-01-18 11:28:50
*/

SET FOREIGN_KEY_CHECKS=0;

----------------------------
-- Table structure for account
----------------------------
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
  `username` varchar(50) NOT NULL,
  `balance` int(11) DEFAULT NULL,
  PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=gbk;

----------------------------
-- Records of account
----------------------------
INSERT INTO `account` VALUES ('rongrong', '300');
INSERT INTO `account` VALUES ('zhangsan', '200');

----------------------------
-- Table structure for book
----------------------------
DROP TABLE IF EXISTS `book`;
CREATE TABLE `book` (
  `isbn` varchar(50) NOT NULL,
  `book_name` varchar(100) DEFAULT NULL,
  `price` int(11) DEFAULT NULL,
  PRIMARY KEY (`isbn`)
) ENGINE=InnoDB DEFAULT CHARSET=gbk;

----------------------------
-- Records of books
----------------------------
INSERT INTO `book` VALUES ('1001', 'java', '100');
INSERT INTO `book` VALUES ('1002', 'python', '60');

----------------------------
-- Table structure for book_stock
----------------------------
DROP TABLE IF EXISTS `book_stock`;
CREATE TABLE `book_stock` (
  `isbn` varchar(50) NOT NULL,
  `bookname` varchar(100) DEFAULT NULL,
  `stock` int(11) DEFAULT NULL,
  PRIMARY KEY (`isbn`)
) ENGINE=InnoDB DEFAULT CHARSET=gbk;

----------------------------
-- Records of book_stock
----------------------------
INSERT INTO `book_stock` VALUES ('1001', 'java', '10');
INSERT INTO `book_stock` VALUES ('1002', 'python', '10');

The knowledge points of the article match the official knowledge files, and you can further learn relevant knowledge. Java skill treeUse JDBC to operate the databaseDatabase operation 132147 people are learning the system