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

相关推荐
space62123278 小时前
在SpringBoot项目中集成MongoDB
spring boot·后端·mongodb
全栈前端老曹10 小时前
【MongoDB】Node.js 集成 —— Mongoose ORM、Schema 设计、Model 操作
前端·javascript·数据库·mongodb·node.js·nosql·全栈
全栈前端老曹2 天前
【MongoDB】深入研究副本集与高可用性——Replica Set 架构、故障转移、读写分离
前端·javascript·数据库·mongodb·架构·nosql·副本集
范纹杉想快点毕业2 天前
状态机设计与嵌入式系统开发完整指南从面向过程到面向对象,从理论到实践的全面解析
linux·服务器·数据库·c++·算法·mongodb·mfc
萧曵 丶3 天前
MongoDB 核心原理 + 高频面试题
数据库·mongodb
青树寒鸦3 天前
wsl的docker备份mongo和迁移
运维·mongodb·docker·容器
范纹杉想快点毕业3 天前
状态机设计模式与嵌入式系统开发完整指南
java·开发语言·网络·数据库·mongodb·设计模式·架构
李钢蛋4 天前
使用 SSH 隧道安全连接远程 MongoDB
安全·mongodb·ssh
·云扬·4 天前
MongoDB高可用方案详解:副本集与分片集群
数据库·mongodb
云边有个稻草人5 天前
金仓 VS MongoDB:国产数据库凭什么成为MongoDB平替首选?
数据库·mongodb·国产数据库·金仓·kingbasees sql