如何在Kotlin中更轻松地使用Jooq交易

Jay*_*ard 6 transactions jooq kotlin

我有使用事务在Kotlin中编写的Jooq代码,有时我想要一个独立的方法作为顶级操作,它将拥有自己的事务,有时希望它与同一事务中的其他方法一起工作.例如,我有两个低级函数actionAbcactionXyz我想要撰写成不同的更高级别的数据的方法和继承他们的交易如果存在的话,否则有自己的.

我知道在Spring或其他框架中可以添加一些注释来验证"需要事务"或"如果没有则创建事务"类型功能.但是如何在不使用这些库的情况下对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
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

我如何更自然,更危险地做到这一点?

注意: 这个问题是由作者故意编写和回答的(答案问题),因此常见问题的Kotlin主题的答案存在于SO中.

Jay*_*ard 8

要解决此问题,您可以使用扩展函数使某些方法仅在事务中可用.首先,我们修复了事务函数,以便有两种风格,一种是顶级,另一种是嵌套事务.

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
}
Run Code Online (Sandbox Code Playgroud)

现在,您的交易将无缝嵌套,并且永远不会出错.因为当用作嵌套事务时,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
         ...
       }
     }
   }
}
Run Code Online (Sandbox Code Playgroud)

现在可以将相同的原理应用于方法,actionAbc并且actionXyz只能从事务中调用它们.

fun DSLContext.actionAbc(parm1: String, parm2: Int) {
   ...
}

fun DSLContext.actionXyz(parm: Date) {
   ...
}
Run Code Online (Sandbox Code Playgroud)

它们不再创建事务,因为它们只能从一个内部调用.它们的使用现在很自然:

fun higherLevelAction(parm1: String, parm2: Date) {
  tx {
    actionAbc(parm1, 45)
    actionXyz(parm2)

    tx {
       // nesting naturally
       ...
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

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
}
Run Code Online (Sandbox Code Playgroud)

现在actionAbc可以独立调用,也可以在另一个事务中调用,编译器将根据接收者决定调用哪个版本.

唯一需要注意的是,如果这些是类方法,那么它们只能在同一个类中调用,因为您无法同时指定实例和接收器来调用方法.

以上示例涵盖了以下情况:

  • 在通话时创建新的交易(尽管来电者可能不知道这种情况正在发生)
  • 仅在调用时继承现有事务(如果仅存在此方法的版本,则在编译时强制执行)
  • 继承现有的,如果没有在调用时创建新事务(在编译时强制执行,当两个存在时调用正确的版本)

如果要拒绝已存在现有事务时调用方法的情况,只需实现扩展版本并抛出异常:

@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 { 
      ...
   } 
}
Run Code Online (Sandbox Code Playgroud)

最后一种情况将由编译器检查,因为使用@Deprecation了水平设置为的注释ERROR.您也可以允许调用,并委托给另一个方法并将弃用设置为WARNINGlevel,以便用户知道可能存在问题,但也可以使用@Suppress("DEPRECATION")调用语句来抑制警告.