解决 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 组合不同字段的条件,可以构建正确的查询。使用上述修复后的代码即可解决问题。

相关推荐
鲲志说1 天前
电子证照系统国产化改造实践:从MongoDB到金仓数据库的平滑迁移与性能优化
大数据·数据库·mongodb·性能优化·数据库开发·数据库架构·金仓数据库
范纹杉想快点毕业1 天前
单片机开发中的队列数据结构详解,队列数据结构在单片机软件开发中的应用详解,C语言
c语言·数据库·stm32·单片机·嵌入式硬件·mongodb·fpga开发
xxy.c1 天前
基于IMX6ULL芯片--I2C总线简单应用
数据库·mongodb
cookqq1 天前
MongoDB源码分析慢日志:从配置到实现的完整解析
数据库·mongodb·nosql·慢日志
金仓数据库2 天前
平替MongoDB | 金仓多模数据库助力电子证照国产化实践
数据库·mongodb
RestCloud2 天前
MongoDB到关系型数据库:JSON字段如何高效转换?
数据库·mysql·mongodb
csdn_aspnet3 天前
在 C# .NETCore 中使用 MongoDB(第 1 部分):驱动程序基础知识和插入文档
mongodb·.netcore
csdn_aspnet3 天前
在 C# .NETCore 中使用 MongoDB(第 3 部分):跳过、排序、限制和投影
mongodb·c#·.netcore
熊文豪4 天前
政务系统国产化的“教科书级“实践:MongoDB平滑迁移成功案例解析
数据库·mongodb·政务·kingbasees·金仓数据库