spring事务传递特性-REQUIRES_NEW和NESTED
spring对于事务的实现的确是它的一大优点,节省了程序员不少时间。
关于事务,有许多可以聊的内容,例如实现方式、实现原理、传递特性等。
本文讨论传递特性中的REQUIRES_NEW,NESTED。
如果想了解更多可以看官网和下面这个url:SpringAOP学习--Spring事务简介及原理_程序源程序的博客-CSDN博客_springaop事务实现原理
一、前言
在学习传递性之前,先了解以下内容。
- spring的事务框架以及有哪些事务管理器
- spring如何使用aop实现事务
- 如何配置事务管理器
1.1事务框架和事务管理器
在spring的官网上:https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#transaction-programmatic
阐述了几个概念:
- 本地事务
- 全局事务
- 声明式事务管理
- 编程式事务管理
大部分时候,我们只考虑本地事务和声明式事务管理。或者说,如果您不需要开发分布式系统,那么一般情况下够了。
下图是spring的事务管理器的类关系图:
从上图可以看出:
- 有两大子类-分别是PlatformTransactionManager和ReactiveTransactionManager
- 我们日常用的是DataSourceTransactionManager
1.2 aop实现事务
官方图,来自于:https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#transaction-programmatic
关于aop的实现,其原理和拦截器的实现是差不多一个道理:进来前先动一动,结束前再动一动。具体略。
网上有解释这个挺清楚的:SpringAOP学习--Spring事务简介及原理_程序源程序的博客-CSDN博客_springaop事务实现原理
1.3 配置事务管理器(本地)
参考 https://blog.csdn.net/weixin_43868443/article/details/119453063
关键内容就是继承实现:TransactionManagementConfigurer
二、名词解析
2.1 REQUIRES_NEW(需要新事务)
望文生义,就是另外开启一个新的事务。这个事务的成功和失败可以不影响其它事务。
把官方的资料搬过来下:Data Access (spring.io)
PROPAGATION_REQUIRES_NEW, in contrast to PROPAGATION_REQUIRED, always uses an independent physical transaction for each affected transaction scope,
never participating in an existing transaction for an outer scope. In such an arrangement, the underlying resource transactions are different and,
hence, can commit or roll back independently, with an outer transaction not affected by an inner transaction’s rollback status and with an inner transaction’s locks released immediately
after its completion. Such an independent inner transaction can also declare its own isolation level,
timeout, and read-only settings and not inherit an outer transaction’s characteristics.
要点:
- 它是独立的
- 它挂起主事务
- 它可以不影响主事务
2.2 NESTED(嵌套)
关键字:嵌套,保存点(savepoint)。后者通常是rdbms的一个概念,意思是可以回滚到某个标记点。
它和requires_new的主要区别:nested和主事务是一个事务,而requires_new是不同的事务。
它和requires_new的共同点:都可以回滚而不影响主事务(对于nested而言其实是一个)
它们都可以达成一个目的,但是由于requires_new是一个独立的事务,所以可以实现许多特别的业务逻辑。
而nested的由于它的特性,所以如果用于记录日志是一个不错的选择。
三、例子
3.1环境准备
业务简介:有一个仓库表和一个销售记录表。当执行一个销售的业务操作的时候需要:
- 开始日志-记录基本信息
- 销售-检验库存,并增减库存。如果库存不满足要求,则回滚销售的操作
- 结束日志
要求:无论销售是否成功,日志都必须记录在数据库。
表设计:
CREATE TABLE inventory(
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
NAME VARCHAR(60) NOT NULL COMMENT '物品名称',
qty DECIMAL(10,2) NOT NULL,
max_qty DECIMAL(10,2) NOT NULL COMMENT '最大库存',
unit_name VARCHAR(20),
last_optime DATETIME ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY(id)
) COMMENT '库存';
ALTER TABLE inventory
ADD UNIQUE INDEX uid_inventory_name (NAME); CREATE TABLE sale_info(
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
NAME VARCHAR(60) NOT NULL,
qty DECIMAL(10,2) NOT NULL,
price DECIMAL(6,2) NOT NULL,
total_amount DECIMAL(16,2) NOT NULL,
add_time DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY(id)
) COMMENT '销售记录';
3.2-代码
1.代码-日志的start方法:
@Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.DEFAULT,rollbackFor=Exception.class)
@Override
public int start(SystemLog log) {
String sql="INSERT INTO system_log (\r\n"
+ " operation_userid,\r\n"
+ " module_name,\r\n"
+ " action_name, \r\n"
+ " parmas\r\n"
+ ")\n"
+ "value(?,?,?,?)";
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTp.update(
new PreparedStatementCreator() {
public PreparedStatement createPreparedStatement(Connection con) throws SQLException
{
PreparedStatement ps = con.prepareStatement(sql,new String[]{ "id"});
ps.setInt(1, log.getOperationUserid());
ps.setString(2, log.getModuleName());
ps.setString(3, log.getActionName());
ps.setString(4, log.getParmas());
return ps;
}
}, keyHolder);
int id= keyHolder.getKey().intValue();
log.setId(Long.valueOf(id));
return id;
}
2.代码-仓库(为了简介,略去一些不必要的信息):
public interface InventoryService {
public int out(String name,BigDecimal qty) throws InvalidDataException;
public int in(String name,BigDecimal qty) throws InvalidDataException;
} @Service
public class InventoryServiceImpl implements InventoryService { @Autowired
private JdbcTemplate jdbcTp; @Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.DEFAULT,rollbackFor=Exception.class)
@Override
public int out(String name, BigDecimal qty) throws InvalidDataException {
if (!isEnough(name,qty.abs())) {
throw new InvalidDataException("库存不足");
}
BigDecimal realQty=qty.abs().multiply(BigDecimal.valueOf(-1));
return updateQty(name,realQty);
} }
3.代码-销售记录
public interface SaleInfoService {
public int sale(String name,BigDecimal qty, BigDecimal price,BigDecimal totalPrice) throws InvalidDataException;
} @Service
public class SaleInfoServiceImpl implements SaleInfoService { @Autowired
JdbcTemplate jdbcTp; @Autowired
private InventoryService inventoryService; @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.DEFAULT, rollbackFor = Exception.class)
@Override
public int sale(String name, BigDecimal qty, BigDecimal price, BigDecimal totalPrice) throws InvalidDataException {
if (qty.compareTo(BigDecimal.valueOf(10000))==1) {
throw new InvalidDataException("一次最多销售10000");
}
inventoryService.out(name, qty);
String sql = "insert into sale_info(name,qty,price,total_amount) values(?,?,?,?)";
return jdbcTp.update(sql, name, qty, price, totalPrice);
}
..... 其余略
}
4.代码-销售服务
public interface SaleMainService {
public PublicReturn sale(SaleInfo saleInfo) throws InvalidDataException;
} @Service
public class SaleMainServiceImpl implements SaleMainService { @Autowired
LogService logService; @Autowired
SaleInfoService saleService; @Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.DEFAULT,rollbackFor=Exception.class)
@Override
public PublicReturn sale(SaleInfo saleInfo) throws InvalidDataException {
/**
* 要保证保证核心的业务逻辑即使失败了,也需要记录日志
*/
SystemLog log=new SystemLog(1,"销售","销售",JSON.toJSONString(saleInfo));
log.setLogStartime(new Date());
logService.start(log);
/**
* 新一个事务-无论是否成功不影响主事务
* 出库,添加销售记录
*/
boolean isFinished=false;
String message="";
try {
saleService.sale(saleInfo.getName(),
saleInfo.getQty(),saleInfo.getPrice(),
saleInfo.getTotalAmount());
isFinished=true;
}
catch(Exception e) {
//这个catch不影响主事务,因为只针对saleService
message=e.getMessage();
System.out.println(e.getMessage());
} if (isFinished) {
log.setActionResult(GlobalConstant.LOG_RESULT_SUCCESSFUL);
}
else {
log.setActionResult(GlobalConstant.LOG_RESULT_UNSUCCESSFUL);
}
log.setLogEndtime(new Date());
logService.end(log);
if (isFinished) {
return PublicReturn.getSuccessful();
}
else {
return PublicReturn.getUnSuccessful(message);
} } }
注:
a.主事务是 SaleMainService.sale,它的传递特性是标准的REQUIRED(如果有用现有的,否则新建一个事务)
b.独立事务是SaleInfoService.sale,它的传递特性是REQUIRES_NEW(独立一个)
c.在主事务中SaleMainService.sale必须使用try..catch来处理独立事务"SaleInfoService.sale"。
当独立事务"SaleInfoService.sale"发生异常的时候,它自己会回滚(spring处理),之后需要捕获这个异常,避免让主事务回滚。
注意:事务代码中,并非不能做异常处理。至于为什么,仔细阅读原生jdbc的事务就明白了。
5.js代码
var settings = {
"url": "http://localhost:9999/spring/tran/addSaleInfo",
"method": "GET",
"timeout": 0,
"headers": {
"Content-Type": "application/json"
},
"data": JSON.stringify({
"name": "大米",
"qty": 0.3,
"price": 1000
}),
}; $.ajax(settings).done(function (response) {
console.log(response);
});
四、小结
应付绝大部分的应用开发,使用本地事务和声明式事务管理即可,这也是spring官网文档说的。
spring的事务的实现实在是很不错。
强烈建议在开始学习之间,先了解rdbms的事务和保存点等等概念。
spring事务传递特性-REQUIRES_NEW和NESTED的更多相关文章
- 事务、事务特性、事务隔离级别、spring事务传播特性
事务.事务特性.事务隔离级别.spring事务传播特性 1.什么是事务: 事务是程序中一系列严密的操作,所有操作执行必须成功完成,否则在每个操作所做的更改将会被撤销,这也是事务的原子性(要么成功, ...
- 什么是事务、事务特性、事务隔离级别、spring事务传播特性
1.什么是事务: 事务是程序中一系列严密的操作,所有操作执行必须成功完成,否则在每个操作所做的更改将会被撤销,这也是事务的原子性(要么成功,要么失败). 2.事务特性: 事务特性分为四个:原子性(At ...
- spring事务传播特性实验(2):PROPAGATION_REQUIRED实验结果与分析
本文延续上一文章(spring事务传播特性实验(1):数据准备),在已经准备好环境的情况下,做如下的实验,以验证spring传播特性,加深对spring传播特性的理解. 本次主要验证PROPAGATI ...
- Spring 事务传递教程_有实例
通过这篇文章,你将学习到Spring框架中中事务的传递 简介 在处理Spring管理的事务时,开发人员可以以传播的方式定义事务的行为.换句话说,开发人员能够决定业务方法如何被封装在逻辑和物理事务中.来 ...
- Spring事务传递
2018-09-25 @Transactional(propagation=Propagation.NEVER) public void update(){ Session s = sessionFa ...
- 数据库与spring事务传播特性
一.spring事务管理的实现原理,基于AOP 1) REQUIRED ,这个是默认的属性 Support a current transaction, create a new one if non ...
- spring 事务传播特性 和隔离级别
事务的几种传播特性1. PROPAGATION_REQUIRED: 如果存在一个事务,则支持当前事务.如果没有事务则开启2. PROPAGATION_SUPPORTS: 如果存在一个事务,支持当前事务 ...
- Spring事务传播特性的浅析——事务方法嵌套调用的迷茫
Spring事务传播机制回顾 Spring事务一个被讹传很广说法是:一个事务方法不应该调用另一个事务方法,否则将产生两个事务.结果造成开发人员在设计事务方法时束手束脚,生怕一不小心就踩到地雷. 其实这 ...
- 什么是事务?事务特性?事务隔离级别?spring事务传播特性?
一.事务的概述 什么是事务? 在数据库中,所谓事务是指一组逻辑操作单元即一组sql语句,当这个单元中的一部分操作失败,整个事务回滚,只有全部正确才完成提交.判断事务是否配置成功的关键点在于出现异常时事 ...
- Spring 事务传播特性
Spring 事务属性一共有四种:传播行为.隔离级别.只读和事务超时 a) 传播行为定义了被调用方法的事务边界. 传播行为 意义 PROPERGATION_MANDATORY 表示方法必须运行在一 ...
随机推荐
- k8s控制节点etcd删除并重新加入
官方参考:https://kubernetes.io/zh-cn/docs/tasks/administer-cluster/configure-upgrade-etcd/ 1.删除etcd节点 cd ...
- MyBatis源码之MyBatis中SQL语句执行过程
MyBatis源码之MyBatis中SQL语句执行过程 SQL执行入口 我们在使用MyBatis编程时有两种方式: 方式一代码如下: SqlSession sqlSession = sqlSessio ...
- 羽夏闲谈—— Kdenlive flatpak 版本解决语音识别找不到 pip3
简述 Kdenlive是在 Linux 上一款比较优秀的剪辑软件,功能比较强大.操作相对容易,能够满足日常剪辑的需要. 解决方案 找到对应的安装目录/var/lib/flatpak/app/o ...
- windows10安装ruby
下载ruby 下载地址: ruby各版本下载地址 https://rubyinstaller.org/downloads/ 2.3.3版本 https://www.cr173.com/soft/142 ...
- scp本地服务器和远程服务器拷贝文件
上传本地文件到服务器 scp 本地路径 用户名@远程服务器ip:远程路径 下载文件 scp 用户名@远程服务器ip:远程路径 本地路径 -r 是上传下载本地目录到远程 远程文件
- ubuntu16下升级python3的版本--升级到3.8
ubuntu16下升级python3的版本,这里是升级到3.8. 1.首先添加安装源,在命令行输入如下命令: $ sudo add-apt-repository ppa:jonathonf/pytho ...
- fastposter发布1.4.3 跨语言的海报生成器
fastposter发布1.4.3 跨语言的海报生成器 v1.4.3 增加golang语言支持,优化生成器代码,完善官方文档 昨天喝了点小9️⃣,发版慢了些. future: 增加golang语言支持 ...
- leaflet 使用turfjs实现前端自定义插值
Turf.js官网地址:http://turfjs.org/ Turf.js中的几何数据组织规范以WKT格式为标准.其官网中包含了详细的接口介绍和样例讲解.这里我给出我们需要用的接口: 参考地址: h ...
- 鸿蒙HarmonyOS实战-Stage模型(服务卡片的模块和创建)
一.服务卡片的模块和创建 在HarmonyOS中,服务卡片是一种提供即时信息和快速操作的小组件,类似于Android中的通知栏.服务卡片可以显示各种类型的信息,包括通知.天气.日历事件.音乐播放器.快 ...
- WPF没修改代码出现InitializeComponent报错的解决办法
问题不在于我们做错了什么,之前还好好的,我们什么都没做,就报错了.这完全不是我们的问题. 我的建议是,直接做绝一点,删除obj和debug文件夹,让他自己重新生成一次