我使用Kotlin和Mongo(使用KMongo),我有多个模型UserEntity
,MovieEntity
等等。他们每个人都使用特定的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
的任何集合?
答案 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
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())
}
注意:
CoroutineCollection
替换为MongoCollection
,就可以轻松地将其转换为阻塞kmongo。 data class DbSession(
@BsonId
val id: Id<DbSession>,
val name: String,
)