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

时间:2016-05-09 14:54:19

标签: transactions 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
    }
  }
}

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

注意: 此问题是由作者(Self-Answered Questions)故意编写和回答的,因此常见问题的Kotlin主题的答案存在于SO中。

1 个答案:

答案 0 :(得分: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
}

现在您的交易将无缝嵌套,永远不会出错。因为当用作嵌套事务时,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")来抑制警告。呼唤声明。