Can't canonicalize query: BadValue Projection cannot have a mix of inclusion and exclusion.

本文深入探讨了在使用MongoDB时遇到的BadValueProjection异常,详细分析了在Java代码中如何正确设置投影字段,避免混合使用包含和排除字段规格导致的错误。并通过具体示例,展示了如何调整代码以解决问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1问题简介

由于在组里项目开发时使用了Mongo数据库和MySQL数据库存储业务数据,基于Spring框架进行开发,因此使用Java代码存取Mongo中的数据是非常常见的编码实现。在使用Mongodb进行编码过程中,需要进行统计某个字段的所有列表,同时按照年月排序,并过滤不需要的字段,却爆出了标题的异常

Can't canonicalize query: BadValue Projection cannot have a mix of inclusion and exclusion.

该问题如何解决呢?

2代码演示

@RequestMapping(value = "/partydues/getPartyDuesProcessing", method = RequestMethod.POST)
    @ResponseBody
    public String getPartyDuesProcessing(@RequestBody JSONObject form) {

        JSONObject ret = new JSONObject();
        if (!form.containsKey("page") || !form.containsKey("pageSize")) {
            ret.element("result", FAILURE).element("msg", "请指定分页参数page和pageSize");
            return ret.toString();
        }
        int page = form.getInt("page");
        int pageSize = form.getInt("pageSize");
        if(page<1) {page = 1;}
        if(pageSize<10) {pageSize=10;}
		//核心代码, 构件queryObject
        QueryBuilder queryBuilder = new QueryBuilder();
        queryBuilder.and("map.dataType").is("processInstance");
        //构件字段过滤对象
        BasicDBObject fieldsObject = new BasicDBObject();
        fieldsObject.put("_class", 0);
        fieldsObject.put("name", 0);
        fieldsObject.put("cname", 0);
	    //**fieldsObject.put("map.userId", 1);**
        fieldsObject.put("map.bid", 0);
        fieldsObject.put("map.pid", 0);
        //构造排序对象
        BasicDBObject sortObject = new BasicDBObject();
        sortObject.put("map.year", -1);
        sortObject.put("map.month", -1);
       //构造Query对象
        BasicQuery query1 = new BasicQuery(queryBuilder.get(), fieldsObject);
        query1.setSortObject(sortObject);
//        Query query = new Query();
//        query.addCriteria(new Criteria("map.dataType").is("processInstance"));
         //也可以顺序执行
//        query1.with(new Sort(Sort.Direction.DESC, "map.year").and(new Sort(Sort.Direction.DESC, "map.month")));

        List<FreeModel> freeModels = null;
        freeModels = freeDao.find(query1, PARTYDUES_TABLE);
        if (freeModels.size() == 0) {
            logger.info("Program cannot find any partydue processes");
            return ret.element("result", FAILURE).element("msg", "无法找到相关的党费收缴进程历史信息").toString();
        }
        List<PageData> pageDatas = new ArrayList<>();
        for (int i=0; i<freeModels.size(); i++) {
            pageDatas.add(freeModels.get(i).getMap());
        }
        long countRecords = pageDatas.size();
        long totalPages = countRecords % pageSize == 0 ? countRecords / pageSize : (countRecords / pageSize + 1);

        List<PageData> rows = partyDuesBusiness.getListByPage(pageDatas, page, pageSize);
        JSONObject data = new JSONObject();
        data.put("totalPage", totalPages);
        data.put("currentPage", page);
        data.put("pageSize", pageSize);
        data.put("totalCount", countRecords);
        data.put("rows", rows);
        ret.element("result", SUCCESS).element("msg", "查询党费收缴历史信息成功");
        ret.element("data", data);
        return ret.toString();
    }

上述代码导致了BadValue Projection异常。
是以为其中出现了

fieldsObject.put("map.userId", 1);

在fieldsObject中不能同时出现为1的值和为0的值。

这种用法是因为有时候不需要将文档中所有键值对都返回。遇到这种情况,可以通过find或者findOne的第二个参数来指定想要的键。这样做即会节省传输的数据量,又能节省客户端解码文档的时间和内存消耗。

A projection cannot contain both include and exclude specifications, except for the exclusion of the _id field. In projections that explicitly include fields, the _id field is the only field that you can explicitly exclude.

Definition

db.collection.find(query, projection)

    Selects documents in a collection or view and returns a cursor to the selected documents.
    
    Parameter 	Type 	Description
    query 	document 	Optional. Specifies selection filter using query operators. To return all documents in a collection, omit this parameter or pass an empty document ({}).
    projection 	document 	Optional. Specifies the fields to return in the documents that match the query filter. To return all fields in the matching documents, omit this parameter. For details, see Projection.
    Returns:	A cursor to the documents that match the query criteria. When the find() method “returns documents,” the method is actually returning a cursor to the documents

.
投影里除了_id以外,要么全是1,要么全是0,否则就报错

> db.user.find({x:{$lt: 3}}, {x:0, y:1})
Error: error: {
        "$err" : "Can't canonicalize query: BadValue Projection cannot have a mix of inclusion and exclusion.",
        "code" : 17287
} 

因此将上述的代码

fieldsObject.put("map.userId", 1);

注释掉即可正常执行。
指定排序字段时可以使用如下两种方式

BasicDBObject sortObject = new BasicDBObject();
            sortObject.put("map.year", -1);
            sortObject.put("map.month", -1);
    
            BasicQuery query1 = new BasicQuery(queryBuilder.get(), fieldsObject);
            query1.setSortObject(sortObject);

也可以通过Query类的With函数进行

query1.with(new Sort(Sort.Direction.DESC, "map.year").and(new Sort(Sort.Direction.DESC, "map.month")));

达到相同的目的

3 问题总结

总之,MongoDB的根本仍然是shell命令,熟练掌握MongoDB的增删改查,聚合框架等仍是最基础的东西。深刻理解Query对象,BasicQuery对象的继承关系,也是写出最符合自己预期的代码的关键所在。

4参考

http://blog.itpub.net/26239116/viewspace-1485417/

### MongoDB 投影错误解决方案 在 MongoDB 查询中,当使用投影操作时,可能会遇到 `Projection cannot have a mix of inclusion and exclusion` 的错误。这是因为 MongoDB 对于投影字段的选择有严格的规则:在一个查询的投影部分,除了 `_id` 字段外,不能同时存在包含(inclusion)和排除(exclusion)的操作。 #### 错误原因分析 MongoDB 要求,在定义投影时,必须明确指定是只包含某些字段还是只排除某些字段。如果尝试在同一查询中既包含又排除字段,则会触发此错误[^1]。例如: ```javascript db.accounts.find({}, { name: 1, balance: 0 }).limit(5); // Error: Projection cannot have a mix of inclusion and exclusion. ``` 上述代码试图通过 `{ name: 1, balance: 0 }` 同时包含 `name` 和排除 `balance`,这违反了 MongoDB 的投影规则。 --- #### 解决方案 ##### 方法一:仅使用包含模式 可以通过显式声明需要返回的所有字段来解决问题。例如,假设只需要获取 `name` 字段而不需要其他字段,可以这样写: ```javascript db.accounts.find({}, { name: 1 }); ``` 在这种情况下,MongoDB 默认不会返回除 `name` 外的任何字段(除非特别指定了 `_id` 字段[^2])。 ##### 方法二:仅使用排除模式 另一种方法是完全依赖排除逻辑。例如,要排除 `balance` 字段并保留其余所有字段,可如下编写查询语句: ```javascript db.accounts.find({}, { balance: 0 }); ``` 此时,MongoDB 将自动返回文档中的所有其他字段(包括默认存在的 `_id` 字段),但不包括 `balance`[^2]。 ##### 方法三:特殊处理 `_id` 字段 如果希望进一步控制 `_id` 是否显示,可以在投影中单独设置其值为 `0` 或 `1`。例如: - 排除 `_id` 并仅包含 `name`: ```javascript db.accounts.find({}, { _id: 0, name: 1 }); ``` - 包含 `_id` 并排除 `balance`: ```javascript db.accounts.find({}, { _id: 1, balance: 0 }); ``` 需要注意的是,即使设置了 `_id: 0`,也仍然可能因为索引或其他配置的原因导致 `_id` 出现在结果集中[^3]。 --- #### 示例代码 以下是几个实际应用的例子,展示如何正确地避免混合包含与排除的情况: ```javascript // 只包含特定字段 db.collection.find({}, { fieldA: 1, fieldB: 1 }); // 只排除特定字段 db.collection.find({}, { fieldC: 0, fieldD: 0 }); // 控制 _id 显示与否 db.collection.find({}, { _id: 0, fieldE: 1 }); ``` 以上每种方式都严格遵循了 MongoDB 关于投影的规则,从而有效规避了潜在的语法错误。 --- ### 总结 为了防止出现 `Projection cannot have a mix of inclusion and exclusion` 的错误,应始终确保投影条件的一致性——即要么全部采用包含策略,要么全部采用排除策略。对于特殊情况下的 `_id` 字段,可以根据需求灵活调整其取值范围。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值