MongoDB Query转Criteria查询抛出重复$or表达式异常如何解决
问题背景
需要实现的MongoDB原生查询逻辑如下:
{$and: [ {$or: [ {"itemNbr": { $in: :#{#itemNbrs} }}, {"deptNbr": { $in: :#{#deptNbrs} }} ]}, {"allocationPlanType": :#{#allocationPlanType}}, {$or: [ {"overallNeedQuantity": { $gte: :#{#showAllocationPlan} }}, {"overallPlannerQuantity": { $gte: :#{#showAllocationPlan} }} ]}, {$or: [ {"itemDesc1": { $eq: :#{#itemName} }}, {"itemDesc2": { $gte: :#{#itemName} }} ]} ]}
初始编写的Spring Data MongoDB Criteria实现代码:
List<Criteria> andExpression = new ArrayList<>(); if (!CollectionUtils.isEmpty(itemNbrs)) { itemNbrQuery = Criteria.where("itemNbr").in(itemNbrs); andExpression.add(itemNbrQuery); } andExpression.add(Criteria.where("allocationPlanType").is(allocationPlanType)); if(Objects.nonNull(itemName)) { itemNameQuery=criteria.orOperator( Criteria.where("itemDesc1").is(itemName), Criteria.where("itemDesc2").is(itemName) ); andExpression.add(itemNameQuery); } if (Objects.isNull(choice)) { Criteria noFltersQuery = criteria.orOperator( Criteria.where("overallNeedQuantity").gte(showAllocationPlan), Criteria.where("overallPlannerQuantity").gte(showAllocationPlan) ); andExpression.add(noFltersQuery); }else { Criteria noFltersQuery = Criteria.where("overallPlannerQuantity").gte(showAllocationPlan); andExpression.add(noFltersQuery); } query.with(pageableRequest); query.addCriteria(criteria.andOperator(andExpression.toArray(new Criteria[andExpression.size()])));
运行时抛出异常:
Due to limitations of the org.bson.Document, you can't add a second '$or' expression specified as '$or : [Document{{overallNeedQuantity=Document{{$gte=1}}}}, Document{{overallPlannerQuantity=Document{{$gte=1}}}}]'. Criteria already contains '$or : [Document{{itemDesc1=COMP CURR PREK WORKBOOK}}, Document{{itemDesc2=COMP CURR PREK WORKBOOK}}]'.
报错原因
- 核心问题是复用了同一个
criteria实例多次调用orOperator()方法。Spring Data MongoDB的Criteria对象在调用操作符方法时,会直接把操作符挂载到当前Criteria对应的BSON文档节点下,而同一个BSON文档不允许存在多个同名的$or键,因此添加第二个$or条件时直接抛出异常。 - 初始代码还存在两个逻辑偏差:一是原生查询第一个
$or块包含deptNbr的in判断,初始实现未覆盖该逻辑;二是itemDesc2的匹配规则原生是$gte,初始代码错写为is精确匹配。
修复方案
每个独立的$or条件块都必须使用全新的空Criteria实例调用orOperator()生成,不要复用全局criteria实例生成多个同级操作符,所有条件组装完成后统一传入andOperator即可。
修复后的完整代码:
List<Criteria> andExpression = new ArrayList<>(); // 组装第一个$or块:itemNbr或deptNbr匹配 List<Criteria> firstOrConditions = new ArrayList<>(); if (!CollectionUtils.isEmpty(itemNbrs)) { firstOrConditions.add(Criteria.where("itemNbr").in(itemNbrs)); } if (!CollectionUtils.isEmpty(deptNbrs)) { firstOrConditions.add(Criteria.where("deptNbr").in(deptNbrs)); } // 两个条件至少存在一个时,才加入and条件组 if (!firstOrConditions.isEmpty()) { andExpression.add(new Criteria().orOperator(firstOrConditions.toArray(new Criteria[0]))); } // 精确匹配allocationPlanType andExpression.add(Criteria.where("allocationPlanType").is(allocationPlanType)); // 组装itemName匹配的$or块 if(Objects.nonNull(itemName)) { Criteria itemNameQuery = new Criteria().orOperator( Criteria.where("itemDesc1").is(itemName), Criteria.where("itemDesc2").gte(itemName) // 修正为gte,与原生查询逻辑一致 ); andExpression.add(itemNameQuery); } // 组装数量过滤的$or块 if (Objects.isNull(choice)) { Criteria quantityFilterQuery = new Criteria().orOperator( Criteria.where("overallNeedQuantity").gte(showAllocationPlan), Criteria.where("overallPlannerQuantity").gte(showAllocationPlan) ); andExpression.add(quantityFilterQuery); } else { andExpression.add(Criteria.where("overallPlannerQuantity").gte(showAllocationPlan)); } query.with(pageableRequest); // 最终用新的criteria实例组装所有and条件 query.addCriteria(new Criteria().andOperator(andExpression.toArray(new Criteria[0])));
关键注意点
- 只要查询中存在多个平级的
$or/$and/$nor等操作符,每个操作符块都必须用独立的new Criteria()实例生成,禁止在同一个Criteria实例上重复调用同类型操作符方法。 - 组装条件时如果某个分支的条件可能为空(比如itemNbrs和deptNbrs都为空的场景),需要做空判断避免生成空的操作符块导致查询逻辑异常。
内容的提问来源于stack exchange,提问作者SUVAM ROY
相关产品推荐
相关产品推荐

