解决 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);
}

上述代码尝试构建一个查询,要求 broadcastTimeplanBroadcastTime 在指定的 startTimeendTime 范围内。但错误的使用方式导致了无效的 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()));
    }
}
修复后的查询

对于 startTimeendTime 都提供的情况,生成的 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 组合 broadcastTimeplanBroadcastTime 的条件,保持了预期的 OR 逻辑,避免生成无效结构。

  3. 避免冲突

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


注意事项

  • 测试验证

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

  • 空值处理

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


总结

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

相关推荐
双叶8363 小时前
(51单片机)LCD显示数据存储(DS1302时钟模块教学)(LCD1602教程)(独立按键教程)(延时函数教程)(I2C总线认识)(AT24C02认识)
c语言·数据库·单片机·嵌入式硬件·mongodb·51单片机·nosql
追逐时光者4 小时前
MongoDB从入门到实战之MongoDB简介
后端·mongodb
昊昊昊昊昊明7 小时前
10天学会嵌入式技术之51单片机-day-3
数据库·mongodb
QX_hao9 小时前
【mongodb】--下载
mongodb
背太阳的牧羊人10 小时前
用 MongoIndexStore 实现对话存档和恢复 & 实现“多用户、多对话线程”场景(像一个 ChatGPT 对话列表那样)
mongodb·chatgpt·llamaindex·对话存档·持久化存储聊天
辰哥单片机设计1 天前
雨滴传感器详解(STM32)
数据库·mongodb
辰哥单片机设计1 天前
非接触式水位传感器详解(STM32)
数据库·mongodb
getapi1 天前
单片机如何通过串口与上位机进行数据交换
单片机·嵌入式硬件·mongodb
随心~稳心1 天前
MongoDB导出和导入数据
数据库·mongodb