此时id为1的用户余额为0,少了50(一本图书价格为50)
14.5.4、隔离级别
14.5.4.1、使用目的
隔离级别一共有四种:
-
读未提交(READ UNCOMMITTED):允许Transaction01读取Transaction02未提交的修改
-
读已提交(READ COMMITTED):要求Transaction01只能读取Transaction02已提交的修改
-
可重复读(REPEATABLE READ):确保Transaction01可以多次从一个字段中读取到相同的值;
即Transaction01执行期间禁止其它事务对这个字段进行更新
-
串行化(SERIALIZABLE):确保Transaction01可以多次从一个表中读取到相同的行;
在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作;
该隔离级别可以避免任何并发问题,但性能十分低下
各个隔离级别解决并发问题的能力见下表:
隔离级别 |
脏读 |
不可重复读 |
幻读 |
READ UNCOMMITTED |
有 |
有 |
有 |
READ COMMITTED |
无 |
有 |
有 |
REPEATABLE READ |
无 |
无 |
有 |
SERIALIZABLE |
无 |
无 |
无 |
-
脏读(DirtyRead):一个事务读取了另一个并行未提交事务写入的数据。
-
不可重复读(Non-RepeatableRead):一个事务重新读取之前读取过的数据,
发现该数据已经被另一个事务(在初始读之后提交)修改。
-
幻读(PhantomRead):一个事务重新执行一个返回符合一个搜索条件的行集合的查询,
发现满足条件的行集合因为另一个最近提交的事务而发生了改变。
各种数据库产品对事务隔离级别的支持程度:
隔离级别 |
Oracle |
MySQL |
SQL Server |
达梦 |
人大金仓 |
READ UNCOMMITTED |
× |
√ |
√ |
√ |
× |
READ COMMITTED |
√(默认) |
√ |
√(默认) |
√(默认) |
√(默认) |
REPEATABLE READ |
× |
√(默认) |
√ |
× |
√ |
SERIALIZABLE |
√ |
√ |
√ |
√ |
√ |
14.5.4.2、使用方式
@Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别(默认且常用)
@Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交
@Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
@Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
@Transactional(isolation = Isolation.SERIALIZABLE)//串行化
14.5.5、传播行为
14.5.5.1、使用目的
当事务方法被另一个事务方法调用时,需要指定事务应该如何传播
14.5.5.1.1、创建结账业务层接口CheckoutService及其实现类
package org.rain.spring.service;
/**
* @author liaojy
* @date 2023/8/29 - 8:07
*/
public interface CheckoutService {
void checkout(Integer[] bookIds, Integer userId);
}
注意:checkout方法进行了事务管理,它调用的buyBook方法也进行了事务管理
package org.rain.spring.service.impl;
import org.rain.spring.service.BookService;
import org.rain.spring.service.CheckoutService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @author liaojy
* @date 2023/8/29 - 8:10
*/
@Service
public class CheckoutServiceImpl implements CheckoutService {
@Autowired
private BookService bookService;
//一次购买多本图书
@Transactional
public void checkout(Integer[] bookIds, Integer userId) {
for (Integer bookId : bookIds) {
bookService.buyBook(bookId,userId);
}
}
}
14.5.5.1.2、在控制层BookController中添加结账方法
@Autowired
private CheckoutService checkoutService;
public void checkout(Integer[] bookIds, Integer userId){
checkoutService.checkout(bookIds, userId);
}
14.5.5.1.3、添加结账的测试方法
@Test
public void testCheckout(){
Integer[] bookIds = {1,2};
bookController.checkout(bookIds,1);
}
14.5.5.1.4、执行结账前的数据
此时id为1的图书(价格为80)库存为100,id为2的图书(价格为50)库存为100
此时id为1的用户余额为100
14.5.5.1.5、执行结账时的异常
14.5.5.1.6、执行结账后的数据
此时id为1的图书(价格为80)库存为100,id为2的图书(价格为50)库存为100;库存没有变化
此时id为1的用户余额为100;余额没有变化
14.5.5.1.7、测试的数据结果分析
-
@Transactional注解的propagation属性的默认值为:Propagation.REQUIRED;
表示如果有已经开启的事务可用,那么就在这个事务中运行。
-
经过观察,购买图书的方法buyBook()在checkout()中被调用,checkout()上有事务注解,因此在此事务中执行。
-
所购买的两本图书的价格为80和50,而用户的余额为100;
因此在购买第二本图书时余额不足失败,导