Spring-04 声明式事务
1、事务的定义
事务就是由 一组逻辑上紧密关联的 多个工作单元(数据库操作)而合并成一个整体,这些操作 要么都执行,要么都不执行。
2、事务的特性:ACID
1)原子性A : 原子即不可再分,表现:一个事务涉及的多个操作在业务逻辑上缺一不可,保证 同一个事务中的操作要不 都提交,要不 都不提交;
2)一致性C :数据的一致性,一个事务中,不管涉及到多少个操作,都 必须保证数据提交的正确性(一致);即:如果在 事务数据处理中, 有一个或者几个操作失败,必须 回退所有的数据操作,恢复到事务处理之前的 统一状态;
3)隔离性I :程序运行过程中,事务是 并发执行的,要求每个 事务之间都是隔离的,互不干扰;
4)持久性D :事务 处理结束,要将数据进行 持久操作,即永久保存。
3、事务的分类:
1)编程式事务-使用jdbc原生的事务处理,可以将事务处理写在业务逻辑代码中,违背aop原则,不推荐;
2)声明式事务-使用事务 注解 @Transactional,可以 声明在方法上,也可以 声明在类上;
*
**优先级**:
* <mark>声明在**类上**,会对**当前类内的所有方式生效**(所有方法都有事务处理);</mark>
* <mark>声明在**方法上**,只会**对当前方法生效**,当类上和方法上同时存在,**方法的优先级高于类**(有些方法,对声明式事务做特殊属性配置);</mark>
4、事务的属性:
4.1 事务的传播行为:propagation属性
事务的传播行为:propagation 属性指定;
当 一个带事务的方法被 另一个带事务的方法调用时(事务嵌套),当前事务如何处理:
- propagation = Propagation. REQUIRED :
- 默认值, 使用调用者的事务(全程就一个事务,如果有 事务嵌套,以 外部事务为主);
- propagation = Propagation. REQUIRES_NEW :
- 将 调用者的 事务直接挂起,自己 重开新的事务处理,结束提交事务,失败回滚;(当 事务嵌套时,内层事务,会 重新开启新事务的处理, 不受外部事务的管理);
4.2 事务的隔离级别:isolation属性
事务的隔离级别:isolation属性指定隔离级别,只有InnoDB支持事务,所有这里说的事务隔离级别指的是InnoDB下的事务隔离级别。
1、读未提交 : 读取其它事务未提交的数据,了解,基本不会使用;
2、读已提交 : oracle的默认事务隔离级别, 同一个事务处理中, 只能读取其它事务提交后的数据(也就是说事务提交之前对其余事务不可见);
3、可重复读 : mysql默认事务隔离级别, 同一个事务处理中, 多次读取同一数据是都是一样的,不受其它事务影响;
4、串行化 : 可以 避免上面 所有并发问题,但是执 行效率最低, 数据一致性最高;
4.3 事务的指定回滚和不会滚
事务的指定回滚和不会滚:Spring在 默认的情况下,是对所有的运行时异常会 执行事务回滚
1、 rollbackFor : 指定回滚异常, 只有产生了指定的异常类型,才会回滚事务;
2、 noRollbackFor : 指定不会滚异常, 产生了指定的异常类型,也不会回滚事务;
4.4 事务的超时时长-了解
1、timeout,指定事务出现异常,没有及时回滚,单位是秒,防止事务超时,占用资源;
4.5 事务的只读-了解
1、readOnly=false,默认,可读可写’;
2、readOnly=true, 代表该事务处理,理论上只允许读取,不能修改(只是通知spring,并不是一个强制选项)
目的就是:提示数据库驱动程序和数据库系统,这个事务并不包含更改数据的操作,那么JDBC驱动程序和数据库就有可能根据这种情况对该事务进行一些特定的优化,比方说不安排相应的数据库锁,以减轻事务对数据库的压力,毕竟事务也是要消耗数据库的资源的。
但是你非要在”只读事务”里面修改数据,也并非不可以,只不过对于数据一致性的保护不像”读写事务”那样保险而已。
5、 环境搭建
5.1主要 jar包
4.3.18.RELEASE
5.1.47
0.9.5.2
org.springframework
spring-jdbc
${spring.version}
org.springframework
spring-orm
${spring.version}
com.mchange
c3p0
${c3p0.version}
org.springframework
spring-tx
${spring.version}
mysql
mysql-connector-java
${mysql.version}
5.2 配置文件
6、测试
5.1 购买一辆车(没有事务嵌套)
TES 购买一辆 AudiQ5;
模拟购买一辆车,主要流程:(1,2,3 整体是一个事务)
1、据买家购买汽车编号,获取汽车详情;
2、扣汽车的库存
3、扣买家的余额
5.1.2 主要业务代码
5.1.2.1 扣用户余额业务
BuyerServiceImpl
如果买家余额不足,直接返回;
@Service
public class BuyerServiceImpl implements BuyerService {
@Autowired
private BuyerDao buyerDao;
@Override
public void subBuyerMoneyByName(String buyerName, Car car) {
// 根据买家姓名,查询买家详情
Buyer buyer = buyerDao.selectBuyerByName(buyerName);
// 判断买家余额是否充足,如果不足,不能继续扣减金额
if(buyer.getMoney() < car.getPrice()){
System.out.println(String.format("****** 买家:%s,余额不足! ------", buyerName));
return; //直接return
}
// 余额充足,执行扣减余额
int row = buyerDao.updateBuyerMoneyById(buyer.getId(), car.getPrice());
System.out.println(String.format("****** 买家:%s,余额扣减成功,影响行数:%s ------", buyerName, row));
}
}
5.1.2.2 扣库存业务
CarsStockServiceImpl
如果库存不足,直接返回;
@Service
public class CarsStockServiceImpl implements CarsStockService {
@Autowired
private CarsStockDao carsStockDao;
@Override
public void subCarsStockBuyId(Car car) {
//根据汽车编号,插叙汽车详情
CarsStock carsStock = carsStockDao.selectCarsStockByCid(car.getId());
//判断库存是否充足,如果不足,不能购买
if(carsStock.getStock()
5.1.2.3 用户买车业务
根据 卖家名字,和汽车编号买车;
BuyCarServiceImpl
@Service("buyCarService") //方便从容器中获取对象
public class BuyCarServiceImpl implements BuyCarService {
@Autowired
private CarDao carDao;
@Autowired
private CarsStockService carStockService;
@Autowired
private BuyerService buyerService;
//根据 卖家名字,和汽车编号买车
@Override
public void buyCar(String buyerName, Integer carId) {
System.out.println(String.format("------ 买家:%s,购买汽车编号:%s 开始 ------",buyerName,carId));
// 根据买家购买汽车编号,获取汽车详情
Car car = carDao.selectCarById(carId);
// 扣买家的余额
buyerService.subBuyerMoneyByName(buyerName, car);
// 扣汽车的库存
carStockService.subCarsStockBuyId(car);
System.out.println(String.format("------ 买家:%s,购买汽车编号:%s 结束 ------",buyerName,carId));
}
}
5.1.3 测试(没有添加事务处理)
5.1.3.1 测试前的数据
- 汽车价格
- 用户余额
- 库存
根据观察,发现用户TES的余额不够买AudiQ5;
5.1.3.2 测试
//没有添加事务处理
@Test
public void testSpringUnUsedTx(){
//获取买车的业务实现对象
BuyCarService buyCarService = context.getBean("buyCarService", BuyCarService.class);
//调用买车的业务方法
buyCarService.buyCar("TES",1);
}
运行结果:
5.1.3.3 测试后的数据
- 用户余额
- 库存
5.1.4 测试 (加上@Transactional 注解添加事务处理)
5.1.4.1 方法上加上@Transactional 注解
@Transactional
public void buyCar(String buyerName, Integer carId) {
...
}
5.1.4.1 测试
恢复初始数据后测试;
5.1.5 测试 (增加异常抛出)
余额不足,没有异常 直接return, 不能触发事务;
需要 抛出自定义异常才会 触发事务处理;
5.1.5.1 自定义异常类
BuyCarException
public class BuyCarException extends RuntimeException {
//生成所有的构造方法
public BuyCarException() {
}
public BuyCarException(String message) {
super(message);
}
public BuyCarException(String message, Throwable cause) {
super(message, cause);
}
public BuyCarException(Throwable cause) {
super(cause);
}
public BuyCarException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
5.1.5.2 抛出异常
当余额或库存不足的时候,抛出自定义异常;
BuyerServiceImpl
@Service
public class BuyerServiceImpl implements BuyerService {
@Autowired
private BuyerDao buyerDao;
@Override
public void subBuyerMoneyByName(String buyerName, Car car) {
Buyer buyer = buyerDao.selectBuyerByName(buyerName);
if(buyer.getMoney() < car.getPrice()){
System.out.println(String.format("****** 买家:%s,余额不足! ------", buyerName));
//return; //没有异常直接return,不能触发事务
//余额不足抛出自定义异常
//*****余额充足,执行扣减余额*****
throw new BuyCarException(String.format("****** 买家:%s,余额不足! ------", buyerName));
}
int row = buyerDao.updateBuyerMoneyById(buyer.getId(), car.getPrice());
System.out.println(String.format("****** 买家:%s,余额扣减成功,影响行数:%s ------", buyerName, row));
}
}
CarsStockServiceImpl
@Service
public class CarsStockServiceImpl implements CarsStockService {
@Autowired
private CarsStockDao carsStockDao;
@Override
public void subCarsStockBuyId(Car car) {
CarsStock carsStock = carsStockDao.selectCarsStockByCid(car.getId());
if(carsStock.getStock()
5.1.5.3 测试
恢复初始数据后测试;
5.1.5.4 测试 (余额充足)
5.1.5.4.1 测试前的数据
- 用户余额
- 库存
5.1.5.4.2测试
5.1.5.4.3 测试后的数据
- 用户余额
- 库存
5.2购买两辆车(有事务嵌套) **对过程理解还有问题
JDG 购买一辆 AudiQ5 和一辆 BmwX3
模拟购物车一次购买两辆车,主要流程:(1,2 整体是一个事务)
1、买第一辆车(1.1,1.2,1.3 整体是一个事务 默认情况内部事务不生效)
1.1 据买家购买汽车编号,获取汽车详情;
1.2扣汽车的库存
1.3扣买家的余额
2、买第二辆车(1.1,1.2,1.3 整体是一个事务 默认情况内部事务不生效)
1.1 据买家购买汽车编号,获取汽车详情;
1.2扣汽车的库存
1.3扣买家的余额
5.2.1 主要业务代码
模拟购物车一次购买两辆车;
多次调用购买一辆汽车业务;
BuyCarCartServiceImpl
@Service("BuyCarCartService" )
public class BuyCarCartServiceImpl implements BuyCarCartService {
@Autowired
private BuyCarService buyCarService;
@Override
@Transactional //购物车外层事务注解,buyCarService接口方法中也有事务注解
public void buyCarCart(String buyerName, List carIds) {
//模拟购物车垢面多辆车,方便演示事务传播行为,一辆一辆购买(单独调用买车接口)
// carIds.forEach(carId -> buyCarService.buyCar(buyerName,carId));
for (int i = 0; i < carIds.size(); i++) {
//异常处理,防止买第一辆车出现异常后,无法购买第二辆车
try{
buyCarService.buyCar(buyerName,carIds.get(i));
}catch (Exception e){
e.printStackTrace();
}
}
}
}
5.2.1 propagation = Propagation.REQUIRED
默认传播特性,以 外部事务 为主;propagation = Propagation.REQUIRED 可以不写;
5.2.1.1 测试前的数据
- 汽车价格
- 用户余额
- 库存
5.2.1.2测试
//测试事务存在 事务嵌套 的传播行为
//购物车结算
@Test
public void testSpring(){
BuyCarCartService buyCarCartService = context.getBean("BuyCarCartService", BuyCarCartService.class);
//调用购物车买车的业务方法
buyCarCartService.buyCarCart("JDG", Arrays.asList(1,2));
}
测试结果:
5.2.1.3测试后的数据
- 用户余额
- 库存
5.2.1.4 总结
通过 查看数据库的数据发现,数据 没有改变,说明事务并 事务已经回滚,也就是说 默认传播特性,以 外部事务 为主;
5.2.3 propagation = Propagation.REQUIRES_NEW
propagation = Propagation.REQUIRES_NEW的传播特性, 内部事务会自己 重开新的事务处理;
5.2.3.1 内部事务注解添加属性参数
buyCar方法
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void buyCar(String buyerName, Integer carId) {
...
}
5.2.3.2 测试
恢复数据再测试;
5.2.3.3测试后的数据
- 用户余额
- 库存
5.2.3.4 总结
通过 查看数据库的数据发现,数据 发生改变,说明 内部事务重新开起 新的事务处理, 不受外部事务的管理;
Original: https://www.cnblogs.com/xiaoqigui/p/16647592.html
Author: 化羽羽
Title: Spring(四)-声明式事务
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/616432/
转载文章受原作者版权保护。转载请注明原作者出处!