我有使用事务在Kotlin中编写的Jooq代码,有时候我希望一个方法独立工作作为顶级操作,它将拥有自己的事务,而其他时候希望它与其他方法一起工作。相同的交易。例如,我有两个较低级别的函数actionAbc
和actionXyz
,我想要组成不同的更高级别的数据方法并继承它们的事务(如果存在),否则有自己的。
我知道在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中。
答案 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
...
}
}
}
}
现在可以将相同的原则应用于方法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
可以独立调用,也可以在另一个事务中调用,编译器将根据接收者决定调用哪个版本。
唯一需要注意的是,如果这些是类方法,那么它们只能在同一个类中调用,因为你不能同时指定实例和接收者来调用方法。
以上示例涵盖了以下情况:
如果要拒绝在已有事务时调用方法的情况,只需实现扩展版本并抛出异常:
@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")
来抑制警告。呼唤声明。