【问题标题】:How can I more easily use Jooq transactions in Kotlin如何在 Kotlin 中更轻松地使用 Jooq 事务
【发布时间】:2016-09-04 06:35:20
【问题描述】:

我有使用事务在 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 中。

【问题讨论】:

    标签: transactions kotlin jooq


    【解决方案1】:

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-12-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-30
      • 2012-01-30
      • 1970-01-01
      相关资源
      最近更新 更多