使用Kotlin在Mongo中进行多个Collection的通用getter

时间:2018-05-03 12:28:12

标签: mongodb generics kotlin

我使用Kotlin和Mongo(使用KMongo),我有多个模型UserEntityMovieEntity等等。他们每个人都使用特定的Dao类来(实际上)执行相同的方法。因此,我尝试使用应该使用这些方法的BaseDao来避免重复。

所以我将通用基础中的特定实体传递为:

class UserDao : BaseDao<UserEntity>() { ... }

此基类实现如下通用方法:

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)
    }
    ...
}

但是,getCollection()方法出现问题:

private inline fun <reified T: Any> getCollection(): MongoCollection<T> {
    return MongoDb.getDatabase().getCollection<T>()
}

每次调用它时都会出现编译错误:

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.

我无法找到正确的方法来做到这一点。我已经检查了这些线程,但我没有让它工作:Generic class type usage in Kotlin&amp; Kotlin abstract class with generic param and methods which use type param

问题:

如何实现这个通用的BaseDao,它应该获得每个孩子Dao的任何集合?

3 个答案:

答案 0 :(得分:0)

JVM在运行时忘记了T中通用BaseDao<T: Any>()的类型,这就是类型推断失败的原因。解决这个问题的方法是在BaseDao的构造函数中传递T的KClass:

open class BaseDao<T: Any>(val kClass: KClass<T>) {
    ...
}

在此之后,给你的reified函数一个接受`KClass:

的参数
private inline fun <reified T: Any> getCollection(val kClass: KClass<T>):  MongoCollection<T> {
    return MongoDb.getDatabase().getCollection<T>()
}

我没有意识到在不将KClass作为参数传递给函数的情况下执行此操作的方法,但这应该可行,因为通用T可以从提供的kClass派生。 `

另一种方法是使BaseDao inline函数中的所有方法都使用具体的泛型并在类上删除泛型。

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)
    }
    ...
}

这样可以派生通用T,因为调用getCollection的方法也已实现。

答案 1 :(得分:0)

解决方案是使用KMongoUtil

将反射用作Zigzago mentioned
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

答案 2 :(得分:0)

(对于KMongo 4.0。+)不需要为每个方法使用经过修饰的泛型,而是可以将此基类用作起点:

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)
    }
}

并在特定的DAO中实现,例如SessionDao

class SessionDao(collection: CoroutineCollection<DbSession>) 
      : BaseDao<DbSession>(collection)

(注意:如果这样感觉更好,可以使用by关键字将继承替换为委派

此dao和其他dao可以通过DI或某种 dao工厂创建:

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())
}


注意:

  1. 此示例适用于基于协程的kmongo,只需将CoroutineCollection替换为MongoCollection,就可以轻松地将其转换为阻塞kmongo。
  2. 我认为文档ID是通过ID容器注释的,这有助于减少错误,因此应以以下方式创建文档:
     data class DbSession(
         @BsonId
         val id: Id<DbSession>,
    
         val name: String,
     )