基于Kotlin与KMongo实现多集合通用BaseDao的方案问询
用Kotlin+KMongo实现泛型BaseDao复用通用方法的完整方案
我经常在KMongo项目里遇到你说的这种Dao层重复代码的问题,用泛型BaseDao抽离通用逻辑是最优雅的解决方案,下面给你一步步拆解实现:
第一步:统一实体类的基础约束
首先咱们得确保所有实体类都具备MongoDB文档的核心标识——_id字段,这样通用CRUD方法才有统一的操作依据。你可以定义一个基础实体类让所有业务实体继承:
import org.litote.kmongo.Id import org.litote.kmongo.newId abstract class BaseEntity { val _id: Id<out BaseEntity> = newId() } // 业务实体示例 data class UserEntity( val username: String, val email: String ) : BaseEntity() data class MovieEntity( val title: String, val releaseYear: Int ) : BaseEntity()
如果不想用继承,也可以直接在每个实体类里声明_id字段,只要保证字段名是_id或者用@BsonId注解标记主键就行。
第二步:实现泛型BaseDao抽象类
接下来咱们定义BaseDao<T>,这里把泛型约束为T : BaseEntity(如果没继承基础类,就用T : Any,但要确保T有主键字段)。核心是通过KMongo获取对应实体的集合,然后封装通用CRUD方法:
import org.litote.kmongo.Id import org.litote.kmongo.MongoDatabase import org.litote.kmongo.deleteOneById import org.litote.kmongo.find import org.litote.kmongo.findOneById import org.litote.kmongo.insertOne import org.litote.kmongo.insertMany import org.litote.kmongo.updateOneById abstract class BaseDao<T : BaseEntity>(private val database: MongoDatabase) { // 通过泛型获取对应的Mongo集合,懒加载提升性能 private val collection by lazy { database.getCollection<T>() } // 根据ID查询单个文档 open fun getById(id: Id<T>): T? { return collection.findOneById(id) } // 查询所有文档 open fun getAll(): List<T> { return collection.find().toList() } // 插入单个文档并返回ID open fun insert(entity: T): Id<T> { collection.insertOne(entity) return entity._id as Id<T> } // 批量插入文档 open fun insertMany(entities: List<T>) { collection.insertMany(entities) } // 根据ID更新文档,返回是否更新成功 open fun updateById(id: Id<T>, entity: T): Boolean { val result = collection.updateOneById(id, entity) return result.modifiedCount > 0 } // 根据ID删除文档,返回是否删除成功 open fun deleteById(id: Id<T>): Boolean { val result = collection.deleteOneById(id) return result.deletedCount > 0 } // 删除所有文档(谨慎使用!) open fun deleteAll(): Long { val result = collection.deleteMany() return result.deletedCount } }
关键细节说明:
- 用
lazy初始化集合:避免提前创建集合实例,提升启动性能 - 依赖
MongoDatabase:可以通过Koin、Spring等依赖注入框架把数据库实例注入到Dao中 - 所有通用方法标记为
open:方便子类根据业务需求重写(比如某些实体需要自定义查询逻辑)
第三步:实现业务专属Dao类
现在你的业务Dao只需要继承BaseDao,专注实现自己的特有方法即可,完全不用重复写通用CRUD:
// UserDao示例 class UserDao(database: MongoDatabase) : BaseDao<UserEntity>(database) { // 自定义业务方法:根据用户名查询用户 fun getByUsername(username: String): UserEntity? { return collection.find("{username: ?}", username).first() } } // MovieDao示例 class MovieDao(database: MongoDatabase) : BaseDao<MovieEntity>(database) { // 自定义业务方法:根据年份查询电影 fun getByReleaseYear(year: Int): List<MovieEntity> { return collection.find("{releaseYear: ?}", year).toList() } }
第四步:使用Dao的示例
最后,你可以通过依赖注入或者直接实例化的方式使用这些Dao:
import org.litote.kmongo.KMongo fun main() { // 初始化MongoDB连接 val client = KMongo.createClient("mongodb://localhost:27017") val database = client.getDatabase("my_database") // 实例化Dao val userDao = UserDao(database) val movieDao = MovieDao(database) // 使用通用方法 val newUser = UserEntity("john_doe", "john@example.com") val userId = userDao.insert(newUser) val foundUser = userDao.getById(userId) println(foundUser) // 使用自定义方法 val movies2023 = movieDao.getByReleaseYear(2023) println(movies2023) }
可选优化:如果实体没有继承BaseEntity
如果你的实体类不想继承BaseEntity,可以调整BaseDao的泛型约束,并通过@BsonId标记主键字段:
// 实体类示例 data class UserEntity( @BsonId val id: Id<UserEntity> = newId(), val username: String, val email: String ) // 调整后的BaseDao abstract class BaseDao<T : Any>(private val database: MongoDatabase) { private val collection by lazy { database.getCollection<T>() } // 这里需要确保T有@BsonId标记的主键字段,类型为Id<T> open fun getById(id: Id<T>): T? { return collection.findOneById(id) } // 其他通用方法和之前一致... }
这样同样能实现通用方法的复用,只是需要每个实体自己声明主键字段。
内容的提问来源于stack exchange,提问作者Blo
相关产品推荐
相关产品推荐

