Morphia:在嵌入对象列表中搜索空值

时间:2012-03-12 01:25:27

标签: java mongodb morphia

我有一个嵌入实体列表:

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

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

我尝试了以下代码(我确实包含列表的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)以获得解决方法

2 个答案:

答案 0 :(得分: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);

答案 1 :(得分:3)

我认为您的架构存在概念性问题。请记住,您始终查询顶级文档。您的查询无效的原因是因为您要求它返回顶级文档,其中至少一个元素具有foo且至少一个元素没有条形值。请注意,这两个条件不需要应用于同一个数组元素。

您可以使用$ 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作为它的值,这应该导致你需要做的事情。

但同样,这将返回顶级文档,这些文档在其嵌入式数组中具有符合这些条件的元素。如果您只想返回匹配元素,则可能需要将嵌入式结构转换为顶级集合。如果你的更新只涉及通过$ position运算符修改匹配元素就足够了:

db.test.update(
    {embedded:{$elemMatch:{foo:{$exists:true}, $or:[{bar:{$exists:false}}, {bar:null}]}}},
    {$set:{'embedded.$.bar':"yay!"}}
)