Reputation: 11988
I use Kotlin and Mongo (with KMongo) and I have multiple models as UserEntity
, MovieEntity
and so on. Each of them use a specific Dao
class to do (actually) the same methods. Therefore, I'm trying to avoid any duplication by using a BaseDao
which should have these methods instead.
So I pass the specific entity in the generic base as:
class UserDao : BaseDao<UserEntity>() { ... }
This base class implements the generic methods as follows:
open class BaseDao<T: Any>() {
fun get(id: String): T? {
return getCollection().findOneById(id)
}
fun save(entity: T): T {
return getCollection().save(entity)
}
fun delete(id: String) {
getCollection().deleteOneById(id)
}
...
}
However, a problem occurs on getCollection()
method:
private inline fun <reified T: Any> getCollection(): MongoCollection<T> {
return MongoDb.getDatabase().getCollection<T>()
}
This gets a compilation error each time I call it:
Type inference failed: Not enough information to infer parameter T in
inline fun <reified T : Any> getCollection(): MongoCollection<T#1 (type parameter of app.api.db.dao.BaseDao.getCollection)>
Please specify it explicitly.
I can't find the right way to do this. I already checked these threads but I didn't make it work: Generic class type usage in Kotlin & Kotlin abstract class with generic param and methods which use type param.
Question:
How can I achieve this generic BaseDao
which should get any collection of each child Dao
?
Upvotes: 2
Views: 1215
Reputation: 7472
(For KMongo 4.0.+) no need to use reified generics for the each method, instead this base class can be used as as a starting point:
open class BaseDao<T: Any>(
protected val collection: CoroutineCollection<T>
) {
suspend fun get(id: Id<T>): T? {
return collection.findOneById(id)
}
suspend fun save(entity: T): UpdateResult? {
return collection.save(entity)
}
suspend fun delete(id: Id<T>) {
collection.deleteOneById(id)
}
}
And implemented in the particular DAO, say SessionDao
:
class SessionDao(collection: CoroutineCollection<DbSession>)
: BaseDao<DbSession>(collection)
(note: inheritance can be replaced with delegation by using by
keyword if one feel better this way
This and other dao can be created via DI or some sort of dao factory:
class DbInstance(mongodbConnectionString: String = "mongodb://localhost:27017/myproject") {
private val connectionInfo = ConnectionString(mongodbConnectionString)
val client = KMongo.createClient().coroutine
val db = client.getDatabase(
connectionInfo.database ?: throw IllegalArgumentException("mongodb connection string must include db name")
)
val sessions = SessionDao(db.getCollection())
}
Notes:
CoroutineCollection
to MongoCollection
data class DbSession(
@BsonId
val id: Id<DbSession>,
val name: String,
)
Upvotes: 1
Reputation: 11988
The solution is to use reflection as Zigzago mentioned by using KMongoUtil
:
protected fun getCollection(): MongoCollection<T> =
getDaoEntityClass().let { k ->
MongoDb.getDatabase().getCollection(
KMongoUtil.defaultCollectionName(k), k.java)
}
@Suppress("UNCHECKED_CAST")
private fun getDaoEntityClass(): KClass<T>
= ((this::class.java.genericSuperclass
as ParameterizedType).actualTypeArguments[0] as Class<T>).kotlin
Upvotes: 0
Reputation: 415
the JVM forgets the type of the generic T
in BaseDao<T: Any>()
at runtime, which is why type inference fails. A solution to this could be to pass the KClass of T in the constructor of BaseDao:
open class BaseDao<T: Any>(val kClass: KClass<T>) {
...
}
After this, give your reified function an argument that accepts a `KClass:
private inline fun <reified T: Any> getCollection(val kClass: KClass<T>): MongoCollection<T> {
return MongoDb.getDatabase().getCollection<T>()
}
I'm unaware of a method to do this without passing the KClass
as a argument to the function, but this should work, as the generic T
can be derived from the provided kClass.
`
Another way would be to make all methods in BaseDao inline function with reified generics and dropping the generic on the class.
open class BaseDao() {
inline fun <reified T: Any> get(id: String): T? {
return getCollection().findOneById(id)
}
inline fun <reified T: Any> save (entity: T): T {
return getCollection().save(entity)
}
inline fun <reified T: Any> delete(id: String) {
getCollection().deleteOneById(id)
}
...
}
This way the generic T
can be derived since the method calling getCollection
is also reified.
Upvotes: 1