2016-05-09 47 views
4

我有使用交易在Kotlin編寫的Jooq代碼,有時我想要一種獨立工作的方法作爲頂級操作,該操作將擁有自己的交易和其他時間希望它能夠在同一事務中與其他方法合成。例如,我有兩個較低級別的函數actionAbcactionXyz,我想將它們組合成不同的更高級別的數據方法,並繼承它們的事務(如果存在),否則將擁有它們自己的事務。如何更輕鬆地在Kotlin中使用Jooq交易

我知道在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),使解答常見科特林主題存在於SO。

回答

6

要解決這個問題,可以使用擴展函數來使某些方法僅在事務中可用。首先,我們修復事務函數,以便有兩種風格,一種是頂層,另一種是嵌套事務。現在

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")來抑制警告。