升级Spring Data Mongo后聚合查询报错且无返回结果
我有一个包含match、unwind、match、group和project阶段的MongoTemplate聚合方法,在Spring Data Mongo 2.2.5.Release(Java Driver 3.11.2)下运行正常,但升级至3.3.5(Java Driver 4.4.2)后,抛出BsonInvalidOperationException错误:readObjectId can only be called when the current bson type is object_id and not document。
给内部文档类UE添加@Id注解后错误消失,但聚合查询的映射结果和原始结果都没有返回值。
错误堆栈
readObjectId can only be called when CurrentBSONType is OBJECT_ID, not when CurrentBSONType is DOCUMENT.; nested exception is org.bson.BsonInvalidOperationException: readObjectId can only be called when CurrentBSONType is OBJECT_ID, not when CurrentBSONType is DOCUMENT. org.springframework.dao.InvalidDataAccessApiUsageException: readObjectId can only be called when CurrentBSONType is OBJECT_ID, not when CurrentBSONType is DOCUMENT.; nested exception is org.bson.BsonInvalidOperationException: readObjectId can only be called when CurrentBSONType is OBJECT_ID, not when CurrentBSONType is DOCUMENT. at org.springframework.data.mongodb.core.MongoExceptionTranslator.translateExceptionIfPossible(MongoExceptionTranslator.java:81) at org.springframework.data.mongodb.core.MongoTemplate.potentiallyConvertRuntimeException(MongoTemplate.java:2929) at org.springframework.data.mongodb.core.MongoTemplate.execute(MongoTemplate.java:562) at org.springframework.data.mongodb.core.MongoTemplate.doAggregate(MongoTemplate.java:2171) at org.springframework.data.mongodb.core.MongoTemplate.doAggregate(MongoTemplate.java:2140) at org.springframework.data.mongodb.core.MongoTemplate.aggregate(MongoTemplate.java:2134) at org.springframework.data.mongodb.core.MongoTemplate.aggregate(MongoTemplate.java:2026)
实体类代码
AU类(主文档)
@Document(collection = "abc") public class AU implements Serializable { private static final long serialVersionUID = -1025499515231580477L; @Id @JsonSerialize(using = ObjectIdJsonSerializer.class) @JsonDeserialize(using = ObjectIdJsonDeserializer.class) public ObjectId id; private String applicationUserId; private String applicationId; private String lastName; private String firstName; private String middleName; private String email; private String title; private String userType; private String lastUpdateBy; private Date lastUpdateDate; private Date createdTime; private Date deletedTime; private boolean active; private String city; private String country; private List<UE> uE = new ArrayList<UE>(); }
UE类(内部文档)
public class UE implements Serializable { private static final long serialVersionUID = 1L; public ObjectId eId; private String eName; private Date lastRecertDate; private String lastRecertBy; private Date createdTime; private String createdBy; private boolean revoked; private Date revokedTime; }
聚合代码
Aggregation aggregation = newAggregation( match(criteria), unwind("ue"), match(where("ue.eId").in(eIds) .and("ue.revoked").ne(Boolean.TRUE)), group(fields) .addToSet("ue").as("ue"), project(fields));
已尝试的方案
- 给UE类的
eId字段添加@BsonProperty("id")和@BsonId注解,无效。 - 调试发现两个版本生成的聚合语句一致,但移除group阶段的
.addToSet("ue").as("ue")后,查询可正常运行。
这个问题是Spring Data MongoDB 3.x版本对嵌套文档的ObjectId映射逻辑变更导致的,具体解决方法如下:
问题根源
升级后,Spring Data MongoDB在处理聚合结果的嵌套文档时,会默认把名为id的字段识别为文档ID(ObjectId类型),但你的UE类中eId对应MongoDB的字段是eId,聚合通过addToSet("ue")返回的嵌套文档里eId是ObjectId类型,框架映射时出现了类型识别错误。而给UE类加@Id注解后,框架会把eId当作UE文档的ID,但聚合结果里的ue是嵌套数组,框架无法正确映射这种结构,导致无返回值。
1. 修正UE类的字段映射
给UE类的eId字段添加@Field注解明确指定MongoDB字段名,避免框架误识别为文档ID:
public class UE implements Serializable { private static final long serialVersionUID = 1L; @Field("eId") public ObjectId eId; // 其他字段保持不变 }
2. 调整聚合的project阶段
确保project阶段明确包含ue字段,避免被过滤:
如果之前的fields变量没包含ue,需要补充进去:
// 基于原有分组字段,添加ue到投影字段中 Fields projectFields = Fields.from(fields).and("ue"); // 替换原project阶段 project(projectFields)
3. 不要给UE类加文档标识注解
UE只是AU的嵌套子文档,不是独立集合的文档,不要给它加@Document或@Id注解。
替代方案(若上述方法无效)
如果聚合映射仍有问题,可以先获取原始Document结果再手动转换为实体类:
AggregationResults<Document> results = mongoTemplate.aggregate(aggregation, "abc", Document.class); List<AU> auList = results.getMappedResults().stream().map(doc -> { AU au = new AU(); // 手动设置AU的各个字段,例如: au.setId(doc.getObjectId("_id")); au.setApplicationUserId(doc.getString("applicationUserId")); // 转换嵌套的ue列表 List<UE> ueList = ((List<Document>) doc.get("ue")).stream().map(ueDoc -> { UE ue = new UE(); ue.setEId(ueDoc.getObjectId("eId")); ue.setEName(ueDoc.getString("eName")); // 设置UE其他字段 return ue; }).collect(Collectors.toList()); au.setuE(ueList); return au; }).collect(Collectors.toList());
内容的提问来源于stack exchange,提问作者GeekCoder

