我怎样才能更轻松地在 Kotlin 中使用 Jooq 事务
How can I more easily use Jooq transactions in Kotlin
我有使用事务在 Kotlin 中编写的 Jooq 代码,有时我希望一个方法作为顶级操作独立工作,它将有自己的事务,而其他时候希望它与其他方法组合工作在同一笔交易中。例如,我有两个较低级别的函数 actionAbc
和 actionXyz
,我想将它们组合成不同的较高级别数据方法并继承它们的事务(如果存在),否则有它们自己的。
我知道在 Spring 或其他框架中可以添加注释来验证“需要交易”或“如果 none 则创建交易”类型的功能。但是我如何在不使用这些库的情况下对 Jooq + Kotlin 做同样的事情呢?
我想到的最接近的方法是将交易作为可选参数传入,如果丢失则默认为新交易。但是如果有人忘记传递交易,那么使用新的顶级和不相关的交易就会出现微妙的失败,我不希望那样。
fun tx(ctx: DSLContext = rootContext, codeBlock: DSLContext.() -> Unit): Unit {
ctx.transaction { cfg ->
DSL.using(cfg).codeBlock()
}
}
}
// and used as:
fun actionAbc(parm1: String, parm2: Int, ctx: DSLContext = rootContext) {
tx(ctx) { ... }
}
fun actionXyz(parm: Date, ctx: DSLContext = rootContext) {
tx(ctx) { ... }
}
// composed:
fun higherLevelAction(parm1: String, parm2: Date) {
tx {
actionAbc(parm1, 45, this) // if you forget `this` you are doing the wrong thing
actionXyz(parm2, this)
tx(this) {
// nested transaction, also dangerous if forgetting `this` parameter
}
}
}
如何更自然、更安全地执行此操作?
注:这个问题是作者(Self-Answered Questions)特意写的,所以常见的Kotlin话题的答案是出现在 SO.
为了解决这个问题,您可以使用扩展函数让一些方法只在一个事务中可用。首先,我们修复交易功能,使其有两种风格,一种是顶级交易,一种是嵌套交易。
fun <T : Any?> tx(codeBlock: DSLContext.() -> T): T {
return rootContext.txWithReturn(codeBlock)
}
fun <T : Any?> DSLContext.tx(codeBlock: DSLContext.() -> T): T {
var returnVal: T? = null
this.transaction { cfg ->
returnVal = DSL.using(cfg).codeBlock()
}
return returnVal as T
}
现在您的事务将无缝嵌套,绝不会出错。因为 Kotlin 在用作嵌套事务时会优先选择更具体的扩展函数。
fun foo() {
tx { // calls the outer function that creates a transaction
...
tx { // calls the extension on DSLContext because our code block has receiver of DSLContext
...
tx { // calls the extension function, further nesting correctly
...
}
}
}
}
现在可以将相同的原则应用于方法 actionAbc
和 actionXyz
,以便它们只能从事务中调用。
fun DSLContext.actionAbc(parm1: String, parm2: Int) {
...
}
fun DSLContext.actionXyz(parm: Date) {
...
}
他们不再创建交易,因为他们保证只能从一个内部调用。它们现在的用途自然是:
fun higherLevelAction(parm1: String, parm2: Date) {
tx {
actionAbc(parm1, 45)
actionXyz(parm2)
tx {
// nesting naturally
...
}
}
}
没有事务就无法调用actionAbc
或actionXyz
。因此,如果你想让它们具有双重用途,我们可以创建第二种风格的操作,它创建自己的事务并委托给另一个。例如 actionAbc
:
fun DSLContext.actionAbc(parm1: String, parm2: Int) {
...
}
fun actionAbc(parm1: String, parm2: Int) {
tx { actionAbc(parm1, parm2) } // delegates to one above but with a new transaction
}
现在 actionAbc
可以独立调用,也可以在另一个事务中调用,编译器将根据接收者决定调用哪个版本。
唯一需要注意的是,如果这些是 class 方法,那么它们只能在同一个 class 中调用,因为您不能同时指定一个实例和一个接收者来调用一种方法。
上面的例子涵盖了这些情况:
- 随叫随到创建新事务(尽管调用者可能不知道这正在发生)
- 仅继承调用时的现有事务(如果仅存在此版本的方法,则在编译时强制执行)
- 继承现有的,如果不创建新的调用事务(在编译时强制执行,当两个存在时调用正确的版本)
如果要拒绝在已经存在事务的情况下调用方法的情况,只需实现扩展版本并抛出异常:
@Deprecated("Only call these without an existing transaction!",
level = DeprecationLevel.ERROR)
fun DSLContext.actionAbc(parm1: String, parm2: Int) {
throw IllegalStateException("Only call these without an existing transaction!")
}
fun actionAbc(parm1: String, parm2: Int) {
tx {
...
}
}
由于使用了级别设置为 ERROR
的 @Deprecation
注释,编译器将检查最后一个案例。您还可以允许调用,并委托给其他方法并将弃用设置为 WARNING
级别,以便用户意识到潜在的问题,但也可以使用 @Suppress("DEPRECATION")
来抑制警告调用语句。
我有使用事务在 Kotlin 中编写的 Jooq 代码,有时我希望一个方法作为顶级操作独立工作,它将有自己的事务,而其他时候希望它与其他方法组合工作在同一笔交易中。例如,我有两个较低级别的函数 actionAbc
和 actionXyz
,我想将它们组合成不同的较高级别数据方法并继承它们的事务(如果存在),否则有它们自己的。
我知道在 Spring 或其他框架中可以添加注释来验证“需要交易”或“如果 none 则创建交易”类型的功能。但是我如何在不使用这些库的情况下对 Jooq + Kotlin 做同样的事情呢?
我想到的最接近的方法是将交易作为可选参数传入,如果丢失则默认为新交易。但是如果有人忘记传递交易,那么使用新的顶级和不相关的交易就会出现微妙的失败,我不希望那样。
fun tx(ctx: DSLContext = rootContext, codeBlock: DSLContext.() -> Unit): Unit {
ctx.transaction { cfg ->
DSL.using(cfg).codeBlock()
}
}
}
// and used as:
fun actionAbc(parm1: String, parm2: Int, ctx: DSLContext = rootContext) {
tx(ctx) { ... }
}
fun actionXyz(parm: Date, ctx: DSLContext = rootContext) {
tx(ctx) { ... }
}
// composed:
fun higherLevelAction(parm1: String, parm2: Date) {
tx {
actionAbc(parm1, 45, this) // if you forget `this` you are doing the wrong thing
actionXyz(parm2, this)
tx(this) {
// nested transaction, also dangerous if forgetting `this` parameter
}
}
}
如何更自然、更安全地执行此操作?
注:这个问题是作者(Self-Answered Questions)特意写的,所以常见的Kotlin话题的答案是出现在 SO.
为了解决这个问题,您可以使用扩展函数让一些方法只在一个事务中可用。首先,我们修复交易功能,使其有两种风格,一种是顶级交易,一种是嵌套交易。
fun <T : Any?> tx(codeBlock: DSLContext.() -> T): T {
return rootContext.txWithReturn(codeBlock)
}
fun <T : Any?> DSLContext.tx(codeBlock: DSLContext.() -> T): T {
var returnVal: T? = null
this.transaction { cfg ->
returnVal = DSL.using(cfg).codeBlock()
}
return returnVal as T
}
现在您的事务将无缝嵌套,绝不会出错。因为 Kotlin 在用作嵌套事务时会优先选择更具体的扩展函数。
fun foo() {
tx { // calls the outer function that creates a transaction
...
tx { // calls the extension on DSLContext because our code block has receiver of DSLContext
...
tx { // calls the extension function, further nesting correctly
...
}
}
}
}
现在可以将相同的原则应用于方法 actionAbc
和 actionXyz
,以便它们只能从事务中调用。
fun DSLContext.actionAbc(parm1: String, parm2: Int) {
...
}
fun DSLContext.actionXyz(parm: Date) {
...
}
他们不再创建交易,因为他们保证只能从一个内部调用。它们现在的用途自然是:
fun higherLevelAction(parm1: String, parm2: Date) {
tx {
actionAbc(parm1, 45)
actionXyz(parm2)
tx {
// nesting naturally
...
}
}
}
没有事务就无法调用actionAbc
或actionXyz
。因此,如果你想让它们具有双重用途,我们可以创建第二种风格的操作,它创建自己的事务并委托给另一个。例如 actionAbc
:
fun DSLContext.actionAbc(parm1: String, parm2: Int) {
...
}
fun actionAbc(parm1: String, parm2: Int) {
tx { actionAbc(parm1, parm2) } // delegates to one above but with a new transaction
}
现在 actionAbc
可以独立调用,也可以在另一个事务中调用,编译器将根据接收者决定调用哪个版本。
唯一需要注意的是,如果这些是 class 方法,那么它们只能在同一个 class 中调用,因为您不能同时指定一个实例和一个接收者来调用一种方法。
上面的例子涵盖了这些情况:
- 随叫随到创建新事务(尽管调用者可能不知道这正在发生)
- 仅继承调用时的现有事务(如果仅存在此版本的方法,则在编译时强制执行)
- 继承现有的,如果不创建新的调用事务(在编译时强制执行,当两个存在时调用正确的版本)
如果要拒绝在已经存在事务的情况下调用方法的情况,只需实现扩展版本并抛出异常:
@Deprecated("Only call these without an existing transaction!",
level = DeprecationLevel.ERROR)
fun DSLContext.actionAbc(parm1: String, parm2: Int) {
throw IllegalStateException("Only call these without an existing transaction!")
}
fun actionAbc(parm1: String, parm2: Int) {
tx {
...
}
}
由于使用了级别设置为 ERROR
的 @Deprecation
注释,编译器将检查最后一个案例。您还可以允许调用,并委托给其他方法并将弃用设置为 WARNING
级别,以便用户意识到潜在的问题,但也可以使用 @Suppress("DEPRECATION")
来抑制警告调用语句。