Reputation: 3175
I am trying to execute a transaction on a Redis instance from within Spring Boot application written in Kotlin. I have followed the recommendation in Spring Doc on the best practice to achieve this.
I am struggling with the Kotlin implementation, however. Specifically, I don't know how to implement the Java generic interface with a generic method to make it work in the Kotlin code:
redisTemplate.execute(object : SessionCallback<List<String>> {
override fun <K : Any?, V : Any?> execute(operations: RedisOperations<K, V>): List<String>? {
operations.multi()
operations.opsForValue().set("key", "value")
return operations.exec()
}
})
The code above complains that the set
method expects parameters with types K
and V
respectively but String
is found instead.
Is there an elegant way how to inline the interface implementation in Kotlin without having to use unchecked casting or other convoluted approaches to make this work?
Upvotes: 2
Views: 1657
Reputation: 8467
I think you're facing this problem due to poor interface definition for SessionCallback
and the framework itself is doing unsafe casts themselves.
You see, if we take a look into the SessionCallback
definition over here we can see that it looks as follows:
public interface SessionCallback<T> {
@Nullable
<K,V> T execute(RedisOperations<K,V> operations) throws DataAccessException
}
The generics K,V
referring to the type of keys and values from your Redis are not parameters of the SessionCallback
interface and that's why the kotlin compiler is having a hard time inferring the type of these: Because the execute
function only takes a parameter of type SessionCallback<T>
without passing the types of keys and values as parameters to that interface.
Your best-effort might be to provide a nice wrapper around that API using extension functions and inline generic types by doing some controlled unsafe casts.
Something like this might be enough:
inline fun <reified K : Any?, reified V: Any?, reified T> RedisTemplate<K, V>.execute(crossinline callback: (RedisOperations<K,V>) -> T?): T?{
val callback = object : SessionCallback<T> {
override fun <KK, VV> execute(operations: RedisOperations<KK,VV>) = callback(operations as RedisOperations<K, V>) as T?
}
return execute(callback)
}
Which then you can consume by doing:
fun doSomething(redisTemplate: RedisTemplate<String, String>) {
redisTemplate.execute { operations ->
operations.multi()
operations.opsForValue().set("key", "value")
operations.exec() as List<String>
}
}
And yes, you need to cast the .exec()
result because nobody bothered using generics and returns a List<Object>
as you can see on the official documentation
Upvotes: 5