1

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 blocks 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
    }
Mateusz Stefek
  • 3,478
  • 2
  • 23
  • 28
  • Is it possible by any chance to create an `Intermediate` class that specifies a `@NonNull` attribute and then create the `wrapper` on top of the `Intermediate` class ? – Nizar Jan 16 '20 at 11:51

1 Answers1

2
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
}
Community
  • 1
  • 1
Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487