MongoDB聚合语句转Morphia实现报错求助
$out与数组_id冲突问题 你遇到的核心问题在于Morphia的.out()方法会触发MongoDB的$out阶段,而原生聚合查询并没有这个环节——原生查询只是直接返回聚合结果,不需要写入集合;但$out要求目标集合的_id不能是数组类型,这就是报错的根源。
错误原因拆解
第一个错误:
Use of undefined variable: roles
当你去掉$符号用roles作为group的_id时,Morphia会把它当成未定义的变量而非字段引用,所以抛出了变量未定义的错误。正确的字段引用必须保留$,也就是"$roles"。第二个错误:
can't use an array for _id
你的原生查询中,_id: "$roles"会生成数组类型的_id(因为roles本身是数组字段),这在聚合返回临时结果时是允许的;但$out需要把结果写入新集合,MongoDB不允许集合的_id是数组类型,因此触发了这个写入失败的错误。而原生查询没有写入集合的操作,所以能正常执行。
两种解决方案
方案一:跳过.out(),直接获取聚合结果
如果你不需要将聚合结果持久化到集合,只是要在代码中处理数据,那么不要使用.out(),改用.iterator()或.asList()直接获取结果:
final Query<User> query = datastore.createQuery(User.class) .field("roles").in(Lists.newArrayList(Role.MEMBER)); // 用iterator代替out,直接获取内存中的聚合结果 final Iterator<AggregatePayments> aggregatePayments = datastore .createAggregation(User.class) .match(query) .group("$roles", grouping("sum", sum("payments." + currentSeason + ".amount"))) .iterator(AggregatePayments.class);
同时确保AggregatePayments类的_id字段对应数组类型:
public class AggregatePayments { private List<String> _id; // 匹配数组类型的聚合结果_id private double sum; // 生成getter、setter方法 }
方案二:修改group的_id为非数组值
由于你已经通过$match过滤出了roles包含MEMBER的文档,可以直接将_id设为固定字符串"MEMBER",这样_id就不是数组了,即使使用.out()也不会报错:
final Query<User> query = datastore.createQuery(User.class) .field("roles").in(Lists.newArrayList(Role.MEMBER)); final Iterator<AggregatePayments> aggregatePayments = datastore .createAggregation(User.class) .match(query) // 用固定字符串作为group的_id,避免数组类型冲突 .group("MEMBER", grouping("sum", sum("payments." + currentSeason + ".amount"))) .out(AggregatePayments.class);
对应的AggregatePayments类_id字段改为字符串类型:
public class AggregatePayments { private String _id; private double sum; // 生成getter、setter方法 }
验证问题根源
你可以手动执行带$out的原生查询,会发现同样触发报错,这能直接验证问题出在$out对_id类型的限制上:
db.user.aggregate([ {$match: { roles: "MEMBER" }}, {$group:{_id: "$roles", sum:{$sum: "$payments.2039.amount"}}}, {$out: "AggregatePayments"} // 加上这个阶段就会和Morphia一样报错 ])
内容的提问来源于stack exchange,提问作者J. Doe

