spring 事务的流传性
事务的流传性
钻研的是多个事务存在时的解决策略
1)REQUIRED:如果存在一个事务,则反对以后事务,如果以后没有事务,就新建一个事务。这是最常见的抉择。
2)SUPPORTS:如果存在一个事务,反对以后事务,如果以后没有事务,就以非事务形式执行。
3)MANDATORY:如果存在一个事务,反对以后事务,如果以后没有事务,就抛出异样。
4)REQUIRES_NEW:新建事务,如果以后存在事务,把以后事务挂起。
5)NOT_SUPPORTED:以非事务形式执行操作,如果以后存在事务,就把以后事务挂起。
6)NEVER:以非事务形式执行,如果以后存在事务,则抛出异样。
7)NESTED:反对以后事务,新增 Savepoint 点,与以后事务同步提交或回滚。
集体整顿了一些材料,有须要的敌人能够间接点击支付。
[Java 基础知识大全](https://docs.qq.com/doc/DTW9N…
)
[22 本 Java 架构师外围书籍](https://docs.qq.com/doc/DTW9N…
)
[从 0 到 1Java 学习路线和材料](https://docs.qq.com/doc/DTW9N…
)
[1000+ 道 2021 年最新面试题](https://docs.qq.com/doc/DTW9N…
)
测试前筹备
筹备好数据库表
数据库 transaction_propagation
账号表 account、书本表 book、库存表 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;
初始化 spring 我的项目
导入一些根本依赖包:jdbc、mysql 驱动包、测试模块;
<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>
数据库连贯信息配置
#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
这里只应用测试调用,省略 controller 以及 entity 等层构;
1、首先 Dao 类,编写一个购买的简略操作:查问单价、更新库存、更新账户余额;
@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、钻研事务传播学性其实就是钻研两个以上的事务在嵌套时的利用形式,所以这里须要写两个 Service 进行嵌套调用;接口类此处省略
BookShopServiceImpl 中的 purchase 用 @Transactional 是指一个购买单进来必须保障(1、2、3)的原子性;
@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);
}
}
CashierServiceImpl 中 buy 办法中的 @Transactional 是指一个订单中呈现多个购买单进来必须保障的原子性;
因为一个订单可能蕴含几种商品的购买。
@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"));
}
}
}
测试类
@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("购买胜利!");
}
}
阐明
以上是应用 spring 默认的事务流传:REQUIRED,purchase 应用同一个事务提交。那就会呈现这样的问题:账户上有 100 块钱,当初提交订单是买一本 60 块钱的《根底数据结构》以及一本 50 块钱的《数据库设计》;那么订单总金额是 110 元,显然账户上余额是不足够购买的,在第一次事务购买 60 块钱的《根底数据结构》是胜利的,但再提交 50 块钱的《数据库设计》的时候就会抛出异样,那这时在 CashierServiceImpl 的外层事务中就会异样并回滚。
应用其余流传性
REQUIRES_NEW
在 purchase 事务中申明 (propagation = Propagation.REQUIRES_NEW);那么在每一次调用 purchase 时都会开启一个新的事务去提交;那么此时进行购买测试后果:第一本书就会购买胜利,第二本书购买失败;因为异样回滚的是第二次调用的 purchase 事务。
@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
会强制要求一个事务提交,否则抛出异样,测试后果与 REQUIRED 一样,回滚整个订单。
@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);
}
如果外层没有事务就抛出异样
No existing transaction found for transaction marked with propagation‘mandatory’
SUPPORTS
如果外层存在事务则以事务提交,测试后果与 REQUIRED 一样,回滚整个订单。
@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);
}
如果外层不存在事务,则不以事务提交,将能够胜利购买一本书;
NOT_SUPPORTED
以非事务形式执行操作,如果以后存在事务,就把以后事务挂起。即外层是否有事务都不会影响后果:有一本书可购买胜利。
@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
强制要求不能存在事务,否则抛出异样
@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);
}
存在事务则抛出异样:
Existing transaction found for transaction marked with propagation‘never’
NESTED
反对以后事务,新增 Savepoint 点,与以后事务同步提交或回滚。后果与 REQUIRES 一样,回滚整个订单。
@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);
}