从jOOQ 3.4开始,我们在jOOQ的JDBC之上有一个简化事务逻辑的API,从jOOQ 3.17和 #13502 开始,在R2DBC之上也将提供一个同等的API,用于反应式应用。

与所有的jOOQ一样,交易是使用显式的、基于API的逻辑实现的。在Jakarta EE和Spring中实现的隐式逻辑对于那些到处使用注解和方面的平台来说非常有效,但基于注解的范式并不适合jOOQ。

本文展示了jOOQ是如何设计事务API的,以及为什么Spring Propagation.NESTED 语义在jOOQ中是默认的。

遵循JDBC的默认值

在JDBC中(和R2DBC一样),一个独立的语句总是非交易性的,或者说是自动提交的。对jOOQ来说也是如此。如果你把一个非交易性的JDBC连接传递给jOOQ,像这样的查询也将是自动提交的:

ctx.insertInto(BOOK).columns(BOOK.ID, BOOK.TITLE).values(1, "Beginning jOOQ").values(2, "jOOQ Masterclass").execute();复制代码

到目前为止还不错,这在大多数API中都是一个合理的默认值。但通常,你不会自动提交。你写的是事务性逻辑。

事务性的lambdas

如果你想在一个事务中运行多个语句,你可以在jOOQ中这样写:

// The transaction() call wraps a transaction
ctx.transaction(trx -> {// The whole lambda expression is the transaction's contenttrx.dsl().insertInto(AUTHOR).columns(AUTHOR.ID, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME).values(1, "Tayo", "Koleoso").values(2, "Anghel", "Leonard").execute();trx.dsl().insertInto(BOOK).columns(BOOK.ID, BOOK.AUTHOR_ID, BOOK.TITLE).values(1, 1, "Beginning jOOQ").values(2, 2, "jOOQ Masterclass").execute();// If the lambda is completed normally, we commit// If there's an exception, we rollback
});复制代码

其心理模型与Jakarta EE和Spring @Transactional 方面完全相同。正常完成隐含地提交,特殊完成隐含地回滚。整个lambda是一个原子的 "工作单元",这是非常直观的。

你拥有你的控制流

如果你的代码中存在任何可恢复的异常,你可以优雅地处理它,而jOOQ的事务管理不会注意到。比如说:

ctx.transaction(trx -> {try {trx.dsl().insertInto(AUTHOR).columns(AUTHOR.ID, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME).values(1, "Tayo", "Koleoso").values(2, "Anghel", "Leonard").execute();}catch (DataAccessException e) {// Re-throw all non-constraint violation exceptionsif (e.sqlStateClass() != C23_INTEGRITY_CONSTRAINT_VIOLATION)throw e;// Ignore if we already have the authors}// If we had a constraint violation above, we can continue our// work here. The transaction isn't rolled backtrx.dsl().insertInto(BOOK).columns(BOOK.ID, BOOK.AUTHOR_ID, BOOK.TITLE).values(1, 1, "Beginning jOOQ").values(2, 2, "jOOQ Masterclass").execute();
});复制代码

在大多数其他API中也是如此,包括Spring。如果Spring不知道你的异常,它就不会将这些异常解释为事务性逻辑,这是很有意义的。毕竟,任何第三方库都可能在  没有注意到的情况下抛出和捕获内部异常,那么为什么Spring要注意呢。

事务传播

Jakarta EE和Spring提供了多种事务传播模式( [TxType](https://jakarta.ee/specifications/platform/8/apidocs/javax/transaction/transactional.txtype) 在Jakarta EE中。 [Propagation](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/annotation/Propagation.html) 在Spring中)。两者中默认的是 REQUIRED 。我一直在努力研究为什么 REQUIRED 是默认的,而不是 NESTED ,我觉得这更符合逻辑和正确,我将在之后解释。如果你知道,请在微博或评论中告诉我:

为什么Propagation.REQUIRED是Spring的默认值?NESTED似乎是一个更好的默认值。

一个NESTED的事务单元是真正的事务。

REQUIRED事务性单元可能会让数据处于一种奇怪的状态,这取决于它是被顶层调用还是从嵌套的范围调用。

- Lukas Eder (@lukaseder)April 28, 2022

我对这些API的假设是:

  1. NESTED 需要 ,这在所有支持事务的RDBMS中都是不存在的。 SAVEPOINT
  2. REQUIRED 避免了 SAVEPOINT 的开销 ,如果你实际上不需要嵌套事务,这可能是一个问题(尽管我们可能会争论说,这样API就被错误地注释了太多的附带的 注释。就像 @Transactional 你不应该无意识地运行 , SELECT * 你也不应该在没有充分考虑的情况下注解所有的东西)。
  3. 在Spring的用户代码中, 每个 服务方法都只是盲目地注解了 @Transactional ,而没有过多地考虑这个话题(和错误处理一样),然后,让事务 REQUIRED ,而不是 NESTED ,这只是一个更方便的默认 "让它工作"。这将有利于 REQUIRED ,更像是一个偶然的默认值,而不是一个精心选择的默认值。
  4. JPA实际上不能很好地使用 NESTED 事务,因为实体会被破坏(见Vlad对此的评论)。在我看来,这只是一个错误或缺失的功能,尽管我可以看到实现这个功能非常复杂,也许在JPA中不值得这样做。

所以,由于所有这些仅仅是技术上的原因,像Jakarta EE或Spring这样的API不把 NESTED ,似乎是可以理解的(Jakarta EE甚至根本就不支持)。

但这是jOOQ,jOOQ一直在退一步思考事情 应该 如何 发展 ,而不是对事情的 现状 印象深刻。

当你想一想下面的代码:

@Transactional
void tx() {tx1();try {tx2();}catch (Exception e) {log.info(e);}continueWorkOnTx1();
}@Transactional
void tx1() { ... }@Transactional
void tx2() { ... }复制代码

写这段代码的程序员的意图只能是一件事:

  • 启动一个全局事务,在 tx()
  • 做一些嵌套的事务性工作,在 tx1()
  • 尝试做一些其他嵌套的事务性工作,在 tx2()
    • 如果 tx2() 成功,很好,继续前进
    • 如果 tx2() 失败,只需记录错误, ROLLBACK 到 tx2() 之前,然后继续。
  • 不管 tx2() ,继续用 tx1() 's(也可能是 tx2() 's)的结果工作。

但这不是 REQUIRED ,它是Jakarta EE和Spring中默认的,会做什么。它将只是回滚 tx2()  tx1() ,让外部事务处于一个非常奇怪的状态,这意味着 continueWorkOnTx1() 将会失败。但它真的应该失败吗? tx2() 应该是一个原子工作单元,与谁调用它无关。默认情况下,它不是这样的,所以 Exception e 必须 被传播。在 catch 块中,在强制重新抛出之前,唯一可以做的事情是清理一些资源或做一些日志记录。(祝你好运,确保每个开发者都遵守这些规则!)

而且,一旦我们强制重新抛出, REQUIRED 就会变得和 NESTED 一样,只是没有了保存点。所以,默认情况是:

NESTED

这是支持将 NESTED 作为默认值的有力论据,至少在jOOQ中是这样。现在,twitter上的讨论涉及到了很多架构方面的问题,比如为什么:

NESTED

我并不反对其中的许多论点。然而,  关注列出的代码,并把自己放在一个库的开发者的位置上,程序员有可能通过这段代码达到什么目的?除了Spring的 NESTED 事务语义之外,我看不出有什么别的东西。我实在看不出来。

jOOQ实现了NESTED语义

由于上述原因,如果支持保存点,jOOQ的事务只实现了Spring的 NESTED 语义,如果不支持保存点,则完全不能嵌套(奇怪的是,这在Jakarta EE和Spring中都不是一个选项,因为这将是另一个合理的默认值)。与Spring的区别在于,所有的事情都是以编程方式明确完成的,而不是隐含地使用方面。

比如说:

ctx.transaction(trx -> {trx.dsl().transaction(trx1 -> {// ..});try {trx.dsl().transaction(trx2 -> {// ..});}catch (Exception e) {log.info(e);}continueWorkOnTrx1(trx);
});复制代码

如果 trx2 出现异常而失败,只有 trx2 被回滚。而不是 trx1 。当然,你仍然可以重新抛出异常来回滚一切。但这里的立场是,如果你,程序员,告诉jOOQ运行一个嵌套事务,那么,jOOQ将服从,因为这是你想要的。

你不可能想要别的东西,因为那样的话,你就不会首先嵌套事务了,不是吗?

R2DBC事务

如前所述,jOOQ 3.17也将(最终)支持R2DBC的事务。其语义与JDBC的阻塞API完全相同,只是现在所有的东西都是 Publisher 。所以,你现在可以写:

Flux<?> flux = Flux.from(ctx.transactionPublisher(trx -> Flux.from(trx.dsl().insertInto(AUTHOR).columns(AUTHOR.ID, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME).values(1, "Tayo", "Koleoso").values(2, "Anghel", "Leonard")).thenMany(trx.dsl().insertInto(BOOK).columns(BOOK.ID, BOOK.AUTHOR_ID, BOOK.TITLE).values(1, 1, "Beginning jOOQ").values(2, 2, "jOOQ Masterclass"))
}));复制代码

这个例子使用reactor作为反应式流API的实现,但你也可以使用RxJava、Mutiny或其他什么。这个例子的工作原理和JDBC的完全一样,最初是这样的。

嵌套的工作方式也是一样的,以通常的、反应式的(也就是更费力的)方式:

Flux<?> flux = Flux.from(ctx.transactionPublisher(trx -> Flux.from(trx.dsl().transactionPublisher(trx1 -> { ... })).thenMany(Flux.from(trx.dsl().transactionPublisher(trx2 -> { ... })).onErrorContinue((e, t) -> log.info(e))).thenMany(continueWorkOnTrx1(trx))
));复制代码

使用 thenMany() 的排序只是一个例子。你可能会发现对完全不同的流构建基元的需求,这些基元与事务管理并无严格关系。

结论

嵌套事务偶尔也是有用的。在jOOQ中,事务传播比Jakarta EE或Spring要少得多,因为你所做的一切通常都是显式的,因此,你不会意外地嵌套事务,当你这样做时,你是故意的。这就是为什么jOOQ选择了与Spring不同的默认值,而且是Jakarta EE完全不支持的默认值。 Propagation.NESTED 语义,这是一种强大的方式,可以将费力的保存点相关逻辑从你的代码中剔除。

jOOQ是如何设计事务API(详细指南)相关推荐

  1. php restful规范,RESTFul API规范 详细指南

    RESTFul规范 RESTFul是一种HTTP API接口规范,只要满足的RESTFul规范,即可称为RESTFul API. 既然是接口,我们先来了解一下,他和传统的API接口有何不同吧. 本文以 ...

  2. PayPal API风格指南和设计模式

    PayPal通过RESTful API将自己的平台打造成彼此连接的服务.他们为此提供了用于创建和使用这些API的指南和设计模式,其他开发者可以在自己的项目中使用这些指南和模式. \\ PayPal的A ...

  3. kafka系列九、kafka事务原理、事务API和使用场景

    一.事务场景 最简单的需求是producer发的多条消息组成一个事务这些消息需要对consumer同时可见或者同时不可见 . producer可能会给多个topic,多个partition发消息,这些 ...

  4. 如何开发API,这样一份完整的API开发指南足够了

    API一词相信每一个开发人员都不陌生,API是软件系统之间或不同组成部分之间进行连接的约定.特别是移动应用程序和微服务架构的不断普及,API就是他们成功背后的功臣,这个时候如何设计和开发API就显得格 ...

  5. Fetch API 简单指南

    Fetch API 简单指南 什么是 Fetch API fetch api 是一个基于Promise api设计的 xmlHttpRequest 的升级替代品,用于通过javascript发起异步请 ...

  6. api怎么写_月薪几十K 的人是怎么设计REST API

    前言 作为一名优秀的后端程序员,你照着产品需求设计好了模型,设计好了关联关系. 把这些模型和关系一再打磨了一番之后,你想是时候把API设计出来,与前端沟通了. 这时候问题来了: 一旦 API 进入前端 ...

  7. 百度地图API开发指南

    百度地图API开发指南 本文为百度地图API官方版本的开发指南,供各位51CTO的网友进行参考. 简介什么是百度地图API? 百度地图API是一套由JavaScript语言编写的应用程序接口,它能够帮 ...

  8. 《设计团队协作权威指南》—第1章1.3节甘为螺丝钉

    本节书摘来自异步社区<设计团队协作权威指南>一书中的第1章1.3节甘为螺丝钉,作者[美]Dan M.Brown,更多章节内容可以访问云栖社区"异步社区"公众号查看. 1 ...

  9. Android App支付系列(一):微信支付接入详细指南(附官方支付demo)

    写在前面 一家移动互联网公司,说到底,要盈利总是需要付费用户的,自己开发支付系统显然是不明智的,国内已经有多家成熟的移动支付提供商,腾讯就是其中之一.梳理了下微信支付的接入,今天给大家分享下腾讯旗下的 ...

最新文章

  1. 深度学习Pytorch框架Tensor张量
  2. 第四章(变量、作用域、内存问题)
  3. 198. House Robber
  4. pod实例数是什么意思_[灌水] Kubernetes In Action: Pod
  5. oracle 12c 低版本,oracle高版本迁移数据到低版本(12c至11g)方法
  6. Ubuntu 20.04安装Ros Noetic及Ubuntu 18.04安装ROS Melodic(两版本详细填坑)
  7. 还怕没女朋友吗?用python做个表白吧
  8. 解析烧录固件失败_Sophos UTM固件反编译Perl源码
  9. 单片机零基础入门(8-5)模块化编程
  10. 基于transformer的车辆识别
  11. 介绍Google Code Playground
  12. bug严重等级与优先级
  13. 宇宙是计算机控制的,宇宙被计算机精确控制到秒,暗能量被否定之后我们活在一个程序中...
  14. 记一次muse-ui 使用
  15. php7 开发框架,Lin是一套基于php7.2的全新web框架
  16. PHP去掉二维数组中某个元素重复的一维数组
  17. centos7 vim查找_如何在 Vim/Vi 中快速查找和替换文本内容
  18. 287. 寻找重复数
  19. Java提供的进制转换
  20. win10Oracle繁体乱码,win10系统切换繁简字体出现乱码的解决方法

热门文章

  1. vue后台系统管理项目-openlayers地图定位、港口数据标记功能
  2. .Net Core 分布式微服务框架介绍 - Jimu
  3. Linux下查找归档的内容
  4. 12个国外免费DNS
  5. Google Play_SDK(官方API)
  6. html中repeat的作用,深入探究CSS repeat()函数知识及用法
  7. 计算机网络复习记录 (2)Chap 4
  8. IOS下载资源zip到本地然后读取
  9. xshell4连接ubuntu,报错No matching outgoing encryption
  10. c语言结构体张三丰,第五次视频我发到网易云课堂上了