I'm trying to write a Kotlin wrapper on Spring's TransactionTemplate
. The crucial code looks like this:
import org.springframework.stereotype.Component
import org.springframework.transaction.PlatformTransactionManager
import org.springframework.transaction.support.TransactionCallback
import org.springframework.transaction.support.TransactionTemplate
@Component
class MyTransactionHelper(
platformTransactionManager: PlatformTransactionManager
) {
private val transactionTemplate = TransactionTemplate(platformTransactionManager);
fun <T> inTransaction(block: () -> T): T {
return transactionTemplate.execute(TransactionCallback<T> { block() })
}
}
The code doesn't compile. This is because Java class TransactionCallback
, defined in Java as:
@FunctionalInterface
public interface TransactionCallback<T> {
@Nullable
T doInTransaction(TransactionStatus status);
}
is interpreted in Kotlin as returning nullable T - T?
, but my inTransaction
method returns T
.
How can I change this code to make it compile, while at the same time allowing my callers to use a nullable type as the generic type?
I could do something like this:
fun <T: Any> inTransaction(block: () -> T): T = transactionTemplate.execute { block() }!!
but then my callers wouldn't be able to pass block
s of type ()->Int?
.
Here's an example code, that I want to compile and run:
val helper: MyTransactionHelper = TODO()
helper.inTransaction {
if (TODO("some irrelevant condition")) 42 else null
}
fun <T : Any> inTransaction(block: () -> T?): T? {
return transactionTemplate.execute(TransactionCallback<T> { block() })
}
(: Any
bound optional). Note that thanks to variance, () -> T
is a subtype of () -> T?
, so users can pass a () -> Int
(and still get Int?
back).
However,
The code doesn't compile. This is because Java class TransactionCallback, defined in Java as...
is interpreted in Kotlin as returning nullable T - T?, but my inTransaction method returns T.
is not correct; the problem is TransactionTemplate.execute
returning T?
, not TransactionCallback.doInTransaction
. And if execute
is actually guaranteed not to return null
when block
doesn't (which I think is true from the documentation, but am not certain), then just add a cast to your original code:
@Suppress("UNCHECKED_CAST")
fun <T> inTransaction(block: () -> T): T {
return transactionTemplate.execute(TransactionCallback<T> { block() }) as T
}