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>
数据库连贯信息配置
#jdbcspring: 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类,编写一个购买的简略操作:查问单价、更新库存、更新账户余额;
@Repositorypublic 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)的原子性;
@Servicepublic 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是指一个订单中呈现多个购买单进来必须保障的原子性;
因为一个订单可能蕴含几种商品的购买。
@Servicepublic 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")); } }}
测试类
@SpringBootTestpublic 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); }