解决 MongoDB 查询中的 `InvalidMongoDbApiUsageException` 错误

您在使用 Spring Data MongoDB 时遇到了 InvalidMongoDbApiUsageException 异常,错误信息如下:

"由于 com.mongodb.BasicDocument 的限制,您无法添加第二个 'null' 条件。查询已经包含 '{ "or" : { "broadcastTime" : { "lte" : { "date" : "2025-04-21T16:00:00Z"}}, "and" : \[{ "broadcastTime" : { "gte" : { "date" : "2025-04-20T16:00:00Z"}}}}, { "planBroadcastTime" : { "lte" : { "date" : "2025-04-21T16:00:00Z"}}, "and" : { "planBroadcastTime" : { "gte" : { "$date" : "2025-04-20T16:00:00Z"}}}}]}'"

这个错误是由使用 Criteria API 构建 MongoDB 查询时的错误用法引起的。以下是问题的分析和解决方案。


问题分析

假设您的代码类似于以下形式,用于根据时间范围和回退条件查询数据:

java 复制代码
Query query = new Query();
if (liveRoomReq.getStartTime() != null && liveRoomReq.getEndTime() != null) {
    Criteria timeCriteria = new Criteria().orOperator(
            Criteria.where("broadcastTime").lte(liveRoomReq.getEndTime())
                    .andOperator(Criteria.where("broadcastTime").gte(liveRoomReq.getStartTime())),
            Criteria.where("planBroadcastTime").lte(liveRoomReq.getEndTime())
                    .andOperator(Criteria.where("planBroadcastTime").gte(liveRoomReq.getStartTime()))
    );
    query.addCriteria(timeCriteria);
}

上述代码尝试构建一个查询,要求 broadcastTime 或 planBroadcastTime 在指定的 startTime 和 endTime 范围内。但错误的使用方式导致了无效的 MongoDB 查询结构。

生成的错误查询

根据错误信息,生成的 MongoDB 查询如下:

json 复制代码
{
  "$or": [
    {
      "broadcastTime": { "$lte": { "$date": "2025-04-21T16:00:00Z" } },
      "$and": [ { "broadcastTime": { "$gte": { "$date": "2025-04-20T16:00:00Z" } } } ]
    },
    {
      "planBroadcastTime": { "$lte": { "$date": "2025-04-21T16:00:00Z" } },
      "$and": [ { "planBroadcastTime": { "$gte": { "$date": "2025-04-20T16:00:00Z" } } } ]
    }
  ]
}

这种结构是无效的,因为 MongoDB 不允许在同一字段的条件中混合使用字段运算符(如 $lte)和逻辑运算符(如 $and)。正确的范围查询应该将 $lte 和 $gte 组合在同一个字段的对象中。

错误原因

问题出在 .andOperator 的误用上:

  • 在 Spring Data MongoDB 中,.andOperator 用于将多个不同字段的条件以 AND 逻辑组合。
  • 对于同一字段的范围查询(如 broadcastTime 需要同时满足 <= endTime 和 >= startTime),应该在单个 Criteria 上直接链式调用 .lte() 和 .gte()。

错误的用法生成了不符合 MongoDB 语法的查询结构,导致 Spring Data MongoDB 在处理后续条件时抛出异常。


解决方案

修复方法是调整 Criteria 的构建方式,在同一字段的条件上直接使用链式调用,而不是使用 .andOperator。以下是更正后的代码:

java 复制代码
Query query = new Query();
if (liveRoomReq.getStartTime() != null && liveRoomReq.getEndTime() != null) {
    Criteria broadcastTimeCriteria = Criteria.where("broadcastTime")
            .lte(liveRoomReq.getEndTime())
            .gte(liveRoomReq.getStartTime());
    Criteria planBroadcastTimeCriteria = Criteria.where("planBroadcastTime")
            .lte(liveRoomReq.getEndTime())
            .gte(liveRoomReq.getStartTime());
    Criteria timeCriteria = new Criteria().orOperator(broadcastTimeCriteria, planBroadcastTimeCriteria);
    query.addCriteria(timeCriteria);
} else if (liveRoomReq.getStartTime() != null) {
    Criteria broadcastTimeCriteria = Criteria.where("broadcastTime")
            .gte(liveRoomReq.getStartTime());
    Criteria planBroadcastTimeCriteria = Criteria.where("planBroadcastTime")
            .gte(liveRoomReq.getStartTime());
    Criteria timeCriteria = new Criteria().orOperator(broadcastTimeCriteria, planBroadcastTimeCriteria);
    query.addCriteria(timeCriteria);
}
// 添加回退条件(示例)
if (StringUtil.isNotEmptyString(liveRoomReq.getFallback())) {
    if ("0".equals(liveRoomReq.getFallback())) {
        Criteria fallbackCriteria = new Criteria().orOperator(
                Criteria.where("fallback").in(liveRoomReq.getFallback()),
                Criteria.where("fallback").exists(false)
        );
        query.addCriteria(fallbackCriteria);
    } else {
        query.addCriteria(Criteria.where("fallback").is(liveRoomReq.getFallback()));
    }
}
修复后的查询

对于 startTime 和 endTime 都提供的情况,生成的 MongoDB 查询如下:

json 复制代码
{
  "$or": [
    { "broadcastTime": { "$lte": "2025-04-21T16:00:00Z", "$gte": "2025-04-20T16:00:00Z" } },
    { "planBroadcastTime": { "$lte": "2025-04-21T16:00:00Z", "$gte": "2025-04-20T16:00:00Z" } }
  ]
}

如果还添加了 fallback 条件(例如 fallback = "0"),最终查询可能是:

json 复制代码
{
  "$and": [
    {
      "$or": [
        { "broadcastTime": { "$lte": "2025-04-21T16:00:00Z", "$gte": "2025-04-20T16:00:00Z" } },
        { "planBroadcastTime": { "$lte": "2025-04-21T16:00:00Z", "$gte": "2025-04-20T16:00:00Z" } }
      ]
    },
    {
      "$or": [
        { "fallback": { "$in": ["0"] } },
        { "fallback": { "$exists": false } }
      ]
    }
  ]
}

这是一个有效的 MongoDB 查询结构。


为什么这个方案有效

  1. 正确的范围查询

    在同一字段的 Criteria 上链式调用 .lte() 和 .gte(),确保条件被正确分组到一个对象中,符合 MongoDB 的语法要求。

  2. 逻辑运算符的正确使用

    使用 .orOperator 组合 broadcastTime 和 planBroadcastTime 的条件,保持了预期的 OR 逻辑,避免生成无效结构。

  3. 避免冲突

    修复后的查询结构消除了格式错误,Spring Data MongoDB 能够正确处理所有条件,不会触发 "second 'null' criteria" 错误。


注意事项

  • 测试验证

    应用修复后,建议使用不同的输入组合(例如,提供 startTime 和 endTime、仅提供 startTime、不同 fallback 值)测试查询,确保结果符合预期。

  • 空值处理

    确保 StringUtil.isNotEmptyString 对 null 和空字符串的处理符合预期,以避免意外添加条件。


总结

InvalidMongoDbApiUsageException 错误源于在同一字段的范围查询中误用 .andOperator,导致无效的 MongoDB 查询结构。通过在每个字段的 Criteria 上直接链式调用 .lte() 和 .gte(),并使用 .orOperator 组合不同字段的条件,可以构建正确的查询。使用上述修复后的代码即可解决问题。

相关推荐
后台模板学习18 小时前
MongoDB 与 MySQL 在 IM 即时聊天场景中该
数据库·mysql·mongodb
ChaITSimpleLove1 天前
Layout.WatchDog.NET 1.5.0-beta.2 发布:.NET 实时应用监控再升级,新增资源监控看板与 SQLite 支持
mysql·mongodb·pgsql·mssql·litedb\sqlite·.net轻量级日志记录器·node资源监控看板
编码追梦人4 天前
基于 ESP32-S3 的便携式多参数环境监护仪设计:从硬件选型到 FreeRTOS 多任务落地
数据库·单片机·嵌入式硬件·mongodb·esp32·freertos·低功耗设计
Brilliantwxx7 天前
【STM32】 独立看门狗 IWDG 深度详解:从硬件结构到工程实战
数据库·stm32·单片机·嵌入式硬件·mongodb
Wang's Blog8 天前
Java 项目实战: 交友项目 - MongoDB集群:概述
java·mongodb·交友
Wang's Blog8 天前
Java 项目实战: 交友项目 - MongoDB基础总结与自检清单
java·mongodb·交友
guo_wen_qiang8 天前
mongodb集群模式的原理
服务器·数据库·mongodb
Wang's Blog9 天前
Java 项目实战: 交友项目 - MongoDB 入门:体系结构和命令行操作
java·mongodb·交友
Elastic 中国社区官方博客9 天前
告别繁琐控制台:Kabanos 如何将 MongoDB Compass 的易用性带入 Elasticsearch
大数据·数据库·mongodb·elasticsearch·搜索引擎·全文检索
guo_wen_qiang9 天前
使用spring-data-mongodb操作mongo记录
数据库·mongodb