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

我有使用事务写在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 } } } 

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

注意: 这个问题是由作者故意编写和回答的( 自我回答问题 ),所以对于常见的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 ... } } } } 

现在同样的原则可以应用于actionAbcactionXyz方法,以便它们只能在事务中被调用。

 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 ... } } } 

没有交易就不可能调用actionAbcactionXyz 。 所以,如果你想使它们双重使用,我们可以创建第二个动作的味道,创建自己的事务,并委托给另一个。 例如对于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可以独立调用,也可以在另一个事务中调用,编译器将根据接收器决定调用哪个版本。

唯一需要注意的是,如果这些是类方法,那么它们只能从同一个类中调用,因为你不能同时指定一个实例和一个接收者来调用一个方法。

上面的例子涵盖了这些情况:

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

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

 @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 { ... } } 

编译器会检查最后一种情况,因为使用了@Deprecation注解,其级别设置为ERROR 。 您也可以允许调用,并委托给其他方法,并将弃用设置为WARNING级别,以便用户知道问题的可能性,但也可以在调用语句中使用@Suppress("DEPRECATION")来抑制警告。