我在MongoDB中有两个集合,父子。下面是文档的结构。
Parent: {
'_id': 'some_value',
'name': 'some_value',
'child': {
'_id': 'some_value',
'name': 'some_value',
'key':'value'
}
}
我正在尝试在MongoRepository方法中传递子ID的列表,以便检索Parent对象,但获取空值。下面是我的代码。
import org.bson.types.ObjectId;
class MyRepository extends CrudRepository<Parent,Long> {
@Query("{'child._id': {$in : ?0 }}")
List<Parent> findByChild_IdIn(List<ObjectId> childIds);
}
我正在调用如下所示的方法。
import org.bson.types.ObjectId;
List<String> childrenIds = getChildrenIdList();
List<ObjectId> childIds = childrenIds.stream().map(childrenId -> new ObjectId(childrenId)).collect(Collectors.toList());
List<Parent> parentsList = myRepository.findByChild_IdIn(childIds);
我在这里做错了什么?为什么给出空值。
答案 0 :(得分:0)
用@MongoId
注释内部文档中的 id 字段。
如本answer中所述,您通常不需要在子文档中使用_id
字段:
_id字段是父文档的必填字段,通常不是必需的,也不存在于嵌入式文档中。如果需要唯一标识符,则可以创建它们,并且如果方便使用代码或思维模型,则可以使用_id字段存储它们。更常见的是,它们以其表示的名称命名(例如“用户名”,“ otherSystemKey”等)。除了顶层文档外,MongoDB本身或任何驱动程序都不会自动填充_id字段。
换句话说,与RDMBS“规范化”模式不同,这里您不需要子文档具有唯一的ID。
尽管您的子文档仍包含某种“ id”字段,该字段引用了另一个集合中的文档(例如“ GoodBoys”),您仍然可能想要。
但是实际上,无论出于何种原因,您可能需要在这些内部子文档中使用一个“唯一”字段。 以下模型将支持建议的结构:
@Data
@Builder
@Document(collection = "parents")
public class Parent {
@Id
private String id;
private String name;
private Child child;
@Data
@Builder
public static class Child {
@MongoId
private String id;
private String name;
private String key;
}
}
您可以通过ID列表检索父母,其中之一:
public interface ParentsRepository extends MongoRepository<Parent, String> {
// QueryDSL
List<Parent> findByChildIdIn(List<String> ids);
// Native Query
@Query("{'child._id': {$in: ?0}}")
List<Parent> findByChildrenIds(List<String> ids);
}
添加一些类似以下内容的父母
parentsRepository.saveAll(Arrays.asList(
Parent.builder()
.name("parent1")
.child(Parent.Child.builder().id(new ObjectId().toString()).name("child1").key("value1").build())
.build(),
Parent.builder()
.name("parent2")
.child(Parent.Child.builder().id(new ObjectId().toString()).name("child2").key("value2").build())
.build()
));
在MongoDB中生成的条目将如下所示:
{
"_id" : ObjectId("5e07384596d9077ccae89a8c"),
"name" : "parent1",
"child" : {
"_id" : "5e07384596d9077ccae89a8a",
"name" : "child1",
"key" : "value1"
},
"_class" : "com.lubumbax.mongoids.model.Parent"
}
请注意此处的两件重要事情:
这种方法适用于这种情况,在这种情况下,我们可以使用new ObjectId().toString()
或其他方式将Java的子代ID插入到MongoDB中,只要得到的子代ID只是MongoDB中的字符串即可。
这意味着在MongoDB中未严格将子级id表示为ObjectId。
如果我们用@MongoId
注释children id字段,则查询结果将类似于:
StringBasedMongoQuery: Created query Document{{child._id=Document{{$in=[5e0740e41095314a3401e49c, 5e0740e41095314a3401e49d]}}}} for Document{{}} fields.
MongoTemplate : find using query: { "child._id" : { "$in" : ["5e0740e41095314a3401e49c", "5e0740e41095314a3401e49d"]}} fields: Document{{}} for class: com.lubumbax.mongoids.model.Parent in collection: parents
如果相反,我们用@Id
注释children id字段,则结果查询将是:
StringBasedMongoQuery: Created query Document{{child._id=Document{{$in=[5e0740e41095314a3401e49c, 5e0740e41095314a3401e49d]}}}} for Document{{}} fields.
MongoTemplate : find using query: { "child._id" : { "$in" : [{ "$oid" : "5e0740e41095314a3401e49c"}, { "$oid" : "5e0740e41095314a3401e49d"}]}} fields: Document{{}} for class: com.lubumbax.mongoids.model.Parent in collection: parents
请注意此处的$oid
。 MongoDB Java驱动程序期望MongoDB中的id属性是实际的ObjectId,因此它将尝试将我们的字符串“投射”到$oid
。
问题在于在MongoDB中,子代的_id属性只是一个字符串,而不是MongoDB的ObjectId。因此,我们的存储库方法找不到我们的父级!
如果我们在MongoDB中插入一个新文档,其中子_id是实际的ObjectId而不是字符串,该怎么办?:
> db.parents.insert({
name: "parent3",
child: {
_id: ObjectId(),
name: "child3",
key: "value3"
}
});
结果条目为:
> db.parents.find({});
{
"_id" : ObjectId("5e074233669d34403ed6bcd2"),
"name" : "parent3",
"child" : {
"_id" : ObjectId("5e074233669d34403ed6bcd1"),
"name" : "child3",
"key" : "value3"
}
}
如果我们现在尝试通过@MongoId
带注释的子_id字段找到该字段,我们将找不到它!
结果查询为:
StringBasedMongoQuery: Created query Document{{child._id=Document{{$in=[5e074233669d34403ed6bcd1]}}}} for Document{{}} fields.
MongoTemplate : find using query: { "child._id" : { "$in" : ["5e074233669d34403ed6bcd1"]}} fields: Document{{}} for class: com.lubumbax.mongoids.model.Parent in collection: parents
为什么?因为现在MongoDB中的_id属性是一个实际的ObjectId,所以我们尝试将其作为纯字符串进行查询。 我们可以通过使用SpEL调整查询来解决此问题,但是恕我直言,我们正在输入“痛苦之地”。
但是,正如我们期望的那样,如果我们使用@Id
注释子_id字段,则会找到该文档:
StringBasedMongoQuery: Created query Document{{child._id=Document{{$in=[5e074233669d34403ed6bcd1]}}}} for Document{{}} fields.
MongoTemplate : find using query: { "child._id" : { "$in" : [{ "$oid" : "5e074233669d34403ed6bcd1"}]}} fields: Document{{}} for class: com.lubumbax.mongoids.model.Parent in collection: parents
再一次,并且如该答案顶部所示,我不鼓励您在子文档中使用ObjectId。
如上所述,我不鼓励任何人在子文档中使用ObjectId。
MongoDB集合中的“父母”条目是唯一的。该文档包含的内容可能代表我们的Java应用程序中的Parent
实体。
与RDBMS相比,这是NoSQL的支柱之一。在RDBMS中,我们倾向于“规范化”我们的模式。
从这个角度来看,没有像“ 文档中的信息的一部分是唯一的”这样的东西,嵌套子元素是唯一的。
最好将孩子称为“孩子元素”(也许对此有一个更好的称呼),而不是“孩子的文件”,因为它们不是实际的文件,而是“父母”文件的一部分(对于那些碰巧遇到的父母)在其结构中包含一个“子”元素)。
如果我们仍然想以某种方式将嵌套的子元素“链接”到另一个集合中的“排他”(或唯一)文档,我们确实可以这样做(见下文)。 我们只是
我认为这是更好的做法。
这个想法是集合中的文档包含了我们代表实体所需要的全部内容。
例如,为了代表父母,除了他们的姓名,年龄和其他有关父母的其他信息外,我们可能只想知道他们孩子的名字(在父母只有一个孩子的世界中) 。
如果需要在某个时候访问这些孩子的更多信息,我们可能会提供另一个包含孩子详细信息的集合。因此,我们可以从父集合中的嵌套子对象“链接”或“引用”到子集合中的文档。
对于孩子们,我们的Java模型看起来像这样:
@Data
@Builder
@Document(collection = "children")
public class Child {
@Id
private String id;
private String name;
private String key;
private Integer age;
}
Parent
实体与Child
实体没有任何“硬”关系:
@Data
@Builder
@Document(collection = "parents")
public class Parent {
@Id
private String id;
private String name;
private ChildData child;
@Data
@Builder
public static class ChildData {
private String id;
private String name;
}
}
请注意,在这种情况下,我更喜欢将嵌套的子对象命名为Parent.ChildData
,因为它仅包含我需要的有关代表父实体的子对象的信息。
还要注意,我没有用@Id
注释嵌套的子ID字段。按照惯例,在这种情况下,MappingMongoConverter
始终会将名为id
的字段映射到mongo _id
字段。
鉴于在MongoDB中(如我们在RDBMS中所理解的),在嵌套的子ID和子ID之间没有关系,我们甚至可以将ChildData.id
字段重命名为ChildData.link
。
您可以在LinkitAir PoC中看到此想法的示例。
那里,Flight
实体模型(存储在MongoDB中的flights
集合中)包含一个嵌套的AirportData
文档,该文档通过其code
字段“引用” Airport
实体(存储在MongoDB中的airports
集合中)。