1

我有一个嵌入式实体列表:

@Embedded
private List<EmbeddedEntity> embedded = new ArrayList<EmbeddedEntity>();

在这个列表中,我想搜索任何具有特定属性(我们称之为foo)的嵌入式实体,而不是另一个(bar)。所以在 Java 中foo应该是非 null 和barnull / 在 MongoDB 中不存在。

我尝试了以下代码(我确实有Entity包含列表的 UUID):

Query<Entity> query = mongoDataStore.find(Entity.class).field("uuid").equal(uuid)
    .field("embedded.foo").exists()
    .field("embedded.bar").doesNotExist();

如果列表为空或只有一个条目(其中foo有一个值bar但尚不存在),这将正常工作。但是只要任何bar属性有值,查询就会返回错误的结果。所以我正在寻找一个查询,它遍历所有嵌入的实体并触发任何缺失bar的 . 那可能吗?

示例数据:

// the query does not pick up the entity as it doesn't have a foo -
// that's what I want
{ uuid: "...", [ { embedded: } ] }

// the query picks up the entity as there is a foo but no bar -
// that's what I want
{ uuid: "...", [ { embedded: { foo: date } } ] }

// the query does not pick up the entity - that's not what I want
// as one foo has a value and its bar doesn't
{ uuid: "...", [ { embedded: { foo: date, bar: date } },
                 { embedded: { foo: date } }
               ] }

PS:我得到相同的结果.field("embedded.bar").hasThisOne(null)

PPS:手动遍历列表元素并不是一个真正的选择,因为我想使用查询进行更新操作。

PPS:我认为这是 Morphia 中的一个错误 - 请参阅下面的答案 ( https://stackoverflow.com/a/9705175/573153 ) 以了解解决方法

4

2 回答 2

4

我找到了解决方法。虽然我似乎无法查询 null,但我可以查询特定值。

就我而言,该bar字段是日期。所以我可以初始化实体private Date bar = new Date(0)- 这显然是我的情况下的无效日期,从未使用过。所以查询看起来像这样:

Query<Entity> query = mongoDataStore
    .find(Entity.class)
    .field("uuid").equal(uuid)
    .field("embedded.foo").exists()
    .field("embedded.bar").hasThisOne(new Date(0));

如果有人需要,这里是更新操作(您需要禁用验证,.$.否则会引发错误):

UpdateOperations<Entity> update = mongoDataStore
    .createUpdateOperations(Entity.class)
    .disableValidation()
    .set("embedded.$.bar", new Date());

mongoDataStore.update(query, update);
于 2012-03-14T15:43:48.860 回答
3

我认为您的架构存在概念问题。请记住,您总是查询顶级文档。您的查询不起作用的原因是因为您要求它返回至少一个元素具有 foo 并且至少一个元素没有 bar 值的顶级文档。请注意,这两个条件不必适用于相同的数组元素。

您可以使用 $elemMatch 在 MongoDB 中做您想做的事情:

find({embedded:{$elemMatch:{foo:{$exists:true}, bar:{$exists:false}}}})如此处所示:

> db.test.save({embedded:[]})
> db.test.save({embedded:[{foo:1}]})
> db.test.save({embedded:[{bar:1}]})
> db.test.save({embedded:[{foo:1, bar:1}]})
> db.test.find({embedded:{$elemMatch:{foo:{$exists:true}, bar:{$exists:false}}}})
{ "_id" : ObjectId("4f60c4d56fa40267a11d2f2c"), "embedded" : [ { "foo" : 1 } ] }

如果“null”是 bar 的有效值,您可以简单地将其更改为:

> db.test.save({embedded:[{foo:1, bar:null}]})
> db.test.find({embedded:{$elemMatch:{foo:{$exists:true}, $or:[{bar:{$exists:false}}, {bar:null}]}}})
{ "_id" : ObjectId("4f60c4d56fa40267a11d2f2c"), "embedded" : [ { "foo" : 1 } ] }
{ "_id" : ObjectId("4f60c52a6fa40267a11d2f30"), "embedded" : [ { "foo" : 1, "bar" : null } ] }

现在,在 Morphia 中,$elemMatch 由 FieldEnd 方法“hasThisElement”包装。我对 Morphia 不太熟悉(我编写并使用了自己的映射器),但这应该采用带有上述子句的 DBObject 作为它的值,这应该会导致您需要做的事情。

但同样,这将返回其嵌入数组中的元素与这些条件匹配的顶级文档。如果您只想返回匹配的元素,您可能必须将嵌入结构转换为顶级集合。如果您的更新仅涉及通过 $ 位置运算符修改匹配元素就足够了:

db.test.update(
    {embedded:{$elemMatch:{foo:{$exists:true}, $or:[{bar:{$exists:false}}, {bar:null}]}}},
    {$set:{'embedded.$.bar':"yay!"}}
)
于 2012-03-14T16:23:48.280 回答