spring对于事务的实现的确是它的一大优点,节省了程序员不少时间。

关于事务,有许多可以聊的内容,例如实现方式、实现原理、传递特性等。

本文讨论传递特性中的REQUIRES_NEW,NESTED。

如果想了解更多可以看官网和下面这个url:SpringAOP学习--Spring事务简介及原理_程序源程序的博客-CSDN博客_springaop事务实现原理

一、前言

在学习传递性之前,先了解以下内容。

  1. spring的事务框架以及有哪些事务管理器
  2. spring如何使用aop实现事务
  3. 如何配置事务管理器

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环境准备

业务简介:有一个仓库表和一个销售记录表。当执行一个销售的业务操作的时候需要:

  1. 开始日志-记录基本信息
  2. 销售-检验库存,并增减库存。如果库存不满足要求,则回滚销售的操作
  3. 结束日志

要求:无论销售是否成功,日志都必须记录在数据库。

表设计:

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的更多相关文章

  1. 事务、事务特性、事务隔离级别、spring事务传播特性

    事务.事务特性.事务隔离级别.spring事务传播特性   1.什么是事务: 事务是程序中一系列严密的操作,所有操作执行必须成功完成,否则在每个操作所做的更改将会被撤销,这也是事务的原子性(要么成功, ...

  2. 什么是事务、事务特性、事务隔离级别、spring事务传播特性

    1.什么是事务: 事务是程序中一系列严密的操作,所有操作执行必须成功完成,否则在每个操作所做的更改将会被撤销,这也是事务的原子性(要么成功,要么失败). 2.事务特性: 事务特性分为四个:原子性(At ...

  3. spring事务传播特性实验(2):PROPAGATION_REQUIRED实验结果与分析

    本文延续上一文章(spring事务传播特性实验(1):数据准备),在已经准备好环境的情况下,做如下的实验,以验证spring传播特性,加深对spring传播特性的理解. 本次主要验证PROPAGATI ...

  4. Spring 事务传递教程_有实例

    通过这篇文章,你将学习到Spring框架中中事务的传递 简介 在处理Spring管理的事务时,开发人员可以以传播的方式定义事务的行为.换句话说,开发人员能够决定业务方法如何被封装在逻辑和物理事务中.来 ...

  5. Spring事务传递

    2018-09-25 @Transactional(propagation=Propagation.NEVER) public void update(){ Session s = sessionFa ...

  6. 数据库与spring事务传播特性

    一.spring事务管理的实现原理,基于AOP 1) REQUIRED ,这个是默认的属性 Support a current transaction, create a new one if non ...

  7. spring 事务传播特性 和隔离级别

    事务的几种传播特性1. PROPAGATION_REQUIRED: 如果存在一个事务,则支持当前事务.如果没有事务则开启2. PROPAGATION_SUPPORTS: 如果存在一个事务,支持当前事务 ...

  8. Spring事务传播特性的浅析——事务方法嵌套调用的迷茫

    Spring事务传播机制回顾 Spring事务一个被讹传很广说法是:一个事务方法不应该调用另一个事务方法,否则将产生两个事务.结果造成开发人员在设计事务方法时束手束脚,生怕一不小心就踩到地雷. 其实这 ...

  9. 什么是事务?事务特性?事务隔离级别?spring事务传播特性?

    一.事务的概述 什么是事务? 在数据库中,所谓事务是指一组逻辑操作单元即一组sql语句,当这个单元中的一部分操作失败,整个事务回滚,只有全部正确才完成提交.判断事务是否配置成功的关键点在于出现异常时事 ...

  10. Spring 事务传播特性

    Spring 事务属性一共有四种:传播行为.隔离级别.只读和事务超时 a)   传播行为定义了被调用方法的事务边界. 传播行为 意义 PROPERGATION_MANDATORY 表示方法必须运行在一 ...

随机推荐

  1. C# 从控制台创建 WinUI 3 应用

    本文将告诉大家如何从控制台而不是 WinUI3 模版项目,从零一步步创建出 WinUI 3 应用 本文不是 WinUI 3 入门博客,本文将从比较基础层的方式创建出 WinUI 3 应用,适合于了解 ...

  2. MyBatis源码之MyBatis中SQL语句执行过程

    MyBatis源码之MyBatis中SQL语句执行过程 SQL执行入口 我们在使用MyBatis编程时有两种方式: 方式一代码如下: SqlSession sqlSession = sqlSessio ...

  3. GitLab 升级迁移待办清单

    GitLab 大版本升级测试用例 项目 从模板项目 URL 导入,来创建新的项目 议题 通过 Quick Actions.关联新建.直接新建 模板 关联项 标签 工时 评论 看板 里程碑 分支 通过 ...

  4. LabView之MQTT协议使用

    一.MQTT概述 MQTT协议是一种消息列队传输协议,采用订阅.发布机制,订阅者只接收自己已经订阅的数据,非订阅数据则不接收,既保证了必要的数据的交换,又避免了无效数据造成的储存与处理.因此在工业物联 ...

  5. 一键入门到精通:sd-webui-prompt-all-in-one 项目大揭秘!

    今天向大家推荐一个宝藏项目.在创意无限的AI艺术生成世界中,sd-webui-prompt-all-in-one 项目如一股清流,为广大创作者和开发者带来了前所未有的便捷和灵感.这不仅仅是一个项目,它 ...

  6. SAP集成技术(一)历史

    最近想读一本书<SAP Interface Management Guide>,打算边读边记录一些笔记.翻译主要由ChatGPT完成. 本文链接:https://www.cnblogs.c ...

  7. 运行程序时报go: cannot find main module, but found .git/config in

    编写单元测试,运行时报下面的错误 haima@haima-PC:/media/haima/34E401CC64DD0E28/site/go/src/haimait/learn/base/cheshi0 ...

  8. three.js介绍和学习资料说明

    1.three.js能做什么 Three.js是基于原生WebGL封装运行的三维引擎,在所有WebGL引擎中,Three.js是国内文资料最多.使用最广泛的三维引擎.既然Threejs是一款WebGL ...

  9. 防止XSS(跨站脚本攻击)漏洞

    点击查看代码 - 输入验证和过滤:对于用户输入的数据,进行严格的验证和过滤.可以使用正则表达式或其他验证方式,确保输入的数据符合预期的格式和内容.同时,对于特殊字符进行转义处理,防止恶意代码的注入. ...

  10. 数据库—SQL语言学习

    文章目录 SQL 数据类型 重要的关键字 定义数据库 数据库的文件 table创建与删除 表的定义 表的alter 表的删除 视图 定义视图 删除视图 更新视图 插入视图 视图总结 索引 SQL单表查 ...