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. [FAQ] wechaty 与 wechaty-puppet-padplus 生态安全吗

    答案是肯定有风险. 非技术角度讲,使用这种方式登录微信存在被微信官方风控的可能性,需要特别注意. 另外,以下是 wechaty 项目说明文件中截取的内容: 我们可以看到,除了微信官方方面的风险,我们的 ...

  2. dotnet 警惕判断文件是否存在因为检查网络资源造成超长等待

    在使用 System.IO.File.Exists 方法时,绝大部分的情况下都是一个非常快捷且没有成本的,但是如果判断的文件是否存在,是从非自己完全控制的逻辑下进入的,那就需要警惕是否判断的文件路径属 ...

  3. Gradle构建SpringBoot单模块项目

    Gradle构建SpringBoot单模块项目 方式Ⅰ:未基于:Gradle Wrapper 方式Ⅱ:(推荐使用)Gradle Wrapper[可以不安装Gradle.统一Gradle的版本]--包括 ...

  4. 【python爬虫案例】爬取微博任意搜索关键词的结果,以“唐山打人”为例

    目录 一.爬取目标 二.展示爬取结果 三.讲解代码 四.同步视频 4.1 演示视频 4.2 讲解视频 五.附:完整源码 一.爬取目标 大家好,我是马哥. 今天分享一期python爬虫案例,爬取目标是新 ...

  5. 游戏陪玩公众号H5软件开发方案图文详解

    用户需求 无论开发怎样的产品,都需要事先对整个市场行情和用户需求进行简单的了解.前面的一组数据已经简明扼要的摆明了现在陪玩市场的行情.而现如今,大多数游戏都需要组队进行,如英雄联盟.王者荣耀.绝地求生 ...

  6. golang 并发问题

    如何使用channel实现定时器? 使用channel的阻塞,里面放一个sleep就可以了 Go语言--goroutine并发模型: 视频地址: https://www.bilibili.com/vi ...

  7. 02. x86处理器运行方式

    [CPU指令] CPU控制器通过读取存储器中的指令确定要执行的功能,CPU运行需要不停的读取指令,计算机启动后CPU会从固定地址处开始读取指令,首先读取 NOR Flash 存储器中的固件,固件执行完 ...

  8. Nifi:Nifi中的Controller Service

    Service简介 首先Nifi中的Controller Service 和我们MVC概念中的Controller Service不是一个概念,Nifi中的Controller Service更像是和 ...

  9. Python 潮流周刊#50:我最喜欢的 Python 3.13 新特性!

    本周刊由 Python猫 出品,精心筛选国内外的 250+ 信息源,为你挑选最值得分享的文章.教程.开源项目.软件工具.播客和视频.热门话题等内容.愿景:帮助所有读者精进 Python 技术,并增长职 ...

  10. jq 工具及其常用用法

    在处理 JSON 数据时,我们经常需要在命令行中进行过滤.查询和编辑的操作.jq 是一个强大的命令行 JSON 处理工具,它可以让我们轻松地对 JSON 数据进行各种操作.本文将简要介绍 jq 的基本 ...