MongoDB Tools API - lifedever/kt-speedy-toolbox GitHub Wiki

MongoDB 工具 API

kt-speedy-data-mongo 模块提供了 Spring Data MongoDB 的增强工具,包括基础实体类、服务层基类和丰富的查询操作。

📋 目录

基础实体类

MongoSupportModel

提供了 MongoDB 基础实体类,包含审计字段和删除标记。

open class MongoSupportModel : Serializable

字段说明:

  • id: ObjectId - MongoDB 主键,自动生成
  • createdDate: Date? - 创建时间,自动设置
  • lastModifiedDate: Date? - 最后修改时间,自动更新
  • createdBy: String? - 创建人
  • lastModifiedBy: String? - 最后修改人
  • deleted: Boolean - 删除标记,默认false

使用示例:

@Document(collection = "users")
class User : MongoSupportModel() {
    var username: String? = null
    var email: String? = null
    var phone: String? = null
    var profile: UserProfile? = null
}

data class UserProfile(
    val firstName: String,
    val lastName: String,
    val avatar: String?
)

基础服务类

MongoSupportService

提供了 MongoDB 通用服务层基类,包含丰富的查询和操作方法。

abstract class MongoSupportService<T : MongoSupportModel>(private val clazz: Class<T>)

必须实现:

protected abstract val template: MongoTemplate

查询操作

基础查询方法

findById()

根据ID查询单个文档。

fun findById(id: ObjectId): T?
fun findById(id: ObjectId, fields: List<String>): T? // 指定返回字段

示例:

val user = userService.findById(ObjectId("507f1f77bcf86cd799439011"))
val userBasic = userService.findById(ObjectId("507f1f77bcf86cd799439011"), listOf("username", "email"))

getOneByFieldIs()

根据字段值查询单个文档。

fun getOneByFieldIs(field: String, isValue: Any?): T?

示例:

val user = userService.getOneByFieldIs("username", "john_doe")
val user2 = userService.getOneByFieldIs("email", "[email protected]")

findByFieldIs()

根据字段值查询多个文档。

fun findByFieldIs(field: String, isValue: Any?): List<T>
fun findByFieldIs(field: String, isValue: Any?, sort: Sort): List<T>
fun findByFieldIs(field: String, isValue: Any?, pageable: Pageable): Page<T>

示例:

val activeUsers = userService.findByFieldIs("status", "active")
val sortedUsers = userService.findByFieldIs("department", "IT", Sort.by("createdDate").descending())
val pagedUsers = userService.findByFieldIs("role", "admin", PageRequest.of(0, 10))

findByFieldIn()

根据字段值在指定集合中查询。

fun findByFieldIn(field: String, values: Collection<Any>): List<T>
fun findByFieldIn(field: String, values: Collection<Any>, sort: Sort): List<T>

示例:

val userIds = listOf("user1", "user2", "user3")
val users = userService.findByFieldIn("userId", userIds)

复杂查询

findByQuery()

使用 Query 对象进行复杂查询。

fun findByQuery(query: Query): List<T>
fun findByQuery(query: Query, pageable: Pageable): Page<T>
fun getOneByQuery(query: Query): T?

示例:

val query = Query()
    .addCriteria(Criteria.where("age").gte(18).lte(65))
    .addCriteria(Criteria.where("status").`is`("active"))
    .with(Sort.by("createdDate").descending())

val adults = userService.findByQuery(query)

findByRegex()

使用正则表达式查询。

fun findByRegex(field: String, regex: String): List<T>
fun findByRegex(field: String, regex: String, options: String): List<T>

示例:

// 查找用户名以 "admin" 开头的用户
val adminUsers = userService.findByRegex("username", "^admin.*")

// 不区分大小写的搜索
val users = userService.findByRegex("email", ".*@gmail\\.com$", "i")

更新操作

字段更新

updateFieldById()

根据ID更新单个字段。

fun updateFieldById(id: ObjectId, key: String, value: Any?): T
fun unsetFieldById(id: ObjectId, key: String): T

示例:

// 更新用户状态
val updatedUser = userService.updateFieldById(userId, "status", "inactive")

// 删除字段
val user = userService.unsetFieldById(userId, "temporaryField")

updateById()

根据ID使用 Update 对象更新。

fun updateById(id: ObjectId, update: Update): T

示例:

val update = Update()
    .set("lastLoginDate", Date())
    .inc("loginCount", 1)
    .addToSet("tags", "frequent_user")

val updatedUser = userService.updateById(userId, update)

updateByQuery()

根据查询条件批量更新。

fun updateByQuery(query: Query, update: Update): UpdateResult
fun updateFirstByQuery(query: Query, update: Update): UpdateResult

示例:

val query = Query.query(Criteria.where("status").`is`("pending"))
val update = Update().set("status", "processed").set("processedDate", Date())

val result = userService.updateByQuery(query, update)
println("更新了 ${result.modifiedCount} 个文档")

聚合操作

count()

统计文档数量。

fun count(): Long
fun count(query: Query): Long

示例:

val totalUsers = userService.count()
val activeUsers = userService.count(Query.query(Criteria.where("status").`is`("active")))

distinct()

获取字段的不重复值。

fun distinct(field: String): List<Any>
fun distinct(field: String, query: Query): List<Any>

示例:

val departments = userService.distinct("department")
val activeDepartments = userService.distinct("department", 
    Query.query(Criteria.where("status").`is`("active")))

aggregate()

执行聚合查询。

fun aggregate(aggregation: Aggregation): AggregationResults<T>
fun <R> aggregate(aggregation: Aggregation, resultClass: Class<R>): AggregationResults<R>

示例:

// 按部门统计用户数量
val aggregation = Aggregation.newAggregation(
    Aggregation.match(Criteria.where("status").`is`("active")),
    Aggregation.group("department").count().`as`("userCount"),
    Aggregation.sort(Sort.Direction.DESC, "userCount")
)

val results = userService.aggregate(aggregation, DepartmentStats::class.java)

data class DepartmentStats(
    val _id: String, // department
    val userCount: Long
)

删除操作

物理删除

deleteById()

根据ID删除文档。

fun deleteById(id: ObjectId)
fun remove(t: T): DeleteResult
fun remove(query: Query): DeleteResult

示例:

userService.deleteById(userId)

val query = Query.query(Criteria.where("status").`is`("inactive"))
val result = userService.remove(query)

逻辑删除

deleteByIdLogic()

逻辑删除(设置deleted字段为true)。

fun deleteByIdLogic(id: ObjectId)

示例:

userService.deleteByIdLogic(userId) // 只是标记删除,不物理删除

使用示例

完整的服务类示例

@Service
class UserService : MongoSupportService<User>(User::class.java) {
    
    @Autowired
    override lateinit var template: MongoTemplate
    
    fun createUser(username: String, email: String, profile: UserProfile): User {
        val user = User().apply {
            this.username = username
            this.email = email
            this.profile = profile
        }
        return save(user)
    }
    
    fun findActiveUsersByDepartment(department: String): List<User> {
        val query = Query()
            .addCriteria(Criteria.where("department").`is`(department))
            .addCriteria(Criteria.where("status").`is`("active"))
            .addCriteria(Criteria.where("deleted").`is`(false))
        
        return findByQuery(query)
    }
    
    fun searchUsersByKeyword(keyword: String, page: Int, size: Int): Page<User> {
        val regex = ".*$keyword.*"
        val query = Query()
            .addCriteria(
                Criteria().orOperator(
                    Criteria.where("username").regex(regex, "i"),
                    Criteria.where("email").regex(regex, "i"),
                    Criteria.where("profile.firstName").regex(regex, "i"),
                    Criteria.where("profile.lastName").regex(regex, "i")
                )
            )
            .addCriteria(Criteria.where("deleted").`is`(false))
        
        val pageable = PageRequest.of(page, size, Sort.by("createdDate").descending())
        return findByQuery(query, pageable)
    }
    
    fun updateUserLastLogin(userId: ObjectId): User {
        val update = Update()
            .set("lastLoginDate", Date())
            .inc("loginCount", 1)
        
        return updateById(userId, update)
    }
    
    fun getUserStatsByDepartment(): List<DepartmentStats> {
        val aggregation = Aggregation.newAggregation(
            Aggregation.match(Criteria.where("deleted").`is`(false)),
            Aggregation.group("department")
                .count().`as`("totalUsers")
                .sum(ConditionalOperators.`when`(Criteria.where("status").`is`("active")).then(1).otherwise(0))
                .`as`("activeUsers"),
            Aggregation.sort(Sort.Direction.DESC, "totalUsers")
        )
        
        return aggregate(aggregation, DepartmentStats::class.java).mappedResults
    }
    
    fun batchUpdateUserStatus(userIds: List<ObjectId>, newStatus: String): Long {
        val query = Query.query(Criteria.where("id").`in`(userIds))
        val update = Update()
            .set("status", newStatus)
            .set("statusUpdatedDate", Date())
        
        return updateByQuery(query, update).modifiedCount
    }
}

data class DepartmentStats(
    val _id: String, // department name
    val totalUsers: Long,
    val activeUsers: Long
)

复杂查询示例

@Service
class ArticleService : MongoSupportService<Article>(Article::class.java) {
    
    @Autowired
    override lateinit var template: MongoTemplate
    
    fun searchArticles(
        keyword: String?,
        author: String?,
        tags: List<String>?,
        publishedAfter: Date?,
        publishedBefore: Date?,
        pageable: Pageable
    ): Page<Article> {
        val criteria = mutableListOf<Criteria>()
        
        // 关键词搜索
        keyword?.let {
            criteria.add(
                Criteria().orOperator(
                    Criteria.where("title").regex(".*$it.*", "i"),
                    Criteria.where("content").regex(".*$it.*", "i")
                )
            )
        }
        
        // 作者筛选
        author?.let {
            criteria.add(Criteria.where("author").`is`(it))
        }
        
        // 标签筛选
        tags?.takeIf { it.isNotEmpty() }?.let {
            criteria.add(Criteria.where("tags").`in`(it))
        }
        
        // 发布时间范围
        publishedAfter?.let {
            criteria.add(Criteria.where("publishedDate").gte(it))
        }
        publishedBefore?.let {
            criteria.add(Criteria.where("publishedDate").lte(it))
        }
        
        // 只查询已发布且未删除的文章
        criteria.add(Criteria.where("published").`is`(true))
        criteria.add(Criteria.where("deleted").`is`(false))
        
        val query = Query()
        if (criteria.isNotEmpty()) {
            query.addCriteria(Criteria().andOperator(*criteria.toTypedArray()))
        }
        
        return findByQuery(query, pageable)
    }
}

📝 注意事项

  1. ObjectId: MongoDB 使用 ObjectId 作为主键,需要正确处理类型转换
  2. 索引: 对于经常查询的字段建议创建索引以提高性能
  3. 聚合查询: 复杂的统计查询建议使用聚合管道
  4. 逻辑删除: 查询时记得过滤已删除的文档(deleted=false)
  5. 分页: 大数据量查询时建议使用分页避免内存问题

🔗 相关链接

⚠️ **GitHub.com Fallback** ⚠️