How to do $pull with Morphia API

1,008 views
Skip to first unread message

daen

unread,
Mar 1, 2011, 8:16:19 PM3/1/11
to Morphia
Hi guys,
here is an example of what I'm trying to do and I need an idea how it
can be achieved.

Java code:
@Entity class Person {

@Embedded private List<Address> addresses; // I want them to be
stored as a part of Person, not in a separate collection
}

@Entity class Address {
private String city;
private String street;
}

Mongodb document ( db.Person.find() )
{ _id:"1",
addresses: [
{ _id:"11", city: "Los Angeles", street: "Ohio st"},
{ _id:"22", city: "San Francisco", street: "Turk st"}
]
}

I want to delete one 'address' from the Person. The mongodb query
works fine:
db.Person.update({_id:"1"}, {$pull:{addresses:{_id:"22"}}}). So I need
to remove an array element based on a value of its property (not its
position in the array).

How I can do same thing with Morphia API?

Thanks.

Scott Hernandez

unread,
Mar 1, 2011, 8:22:21 PM3/1/11
to mor...@googlegroups.com
On Tue, Mar 1, 2011 at 5:16 PM, daen <d.ser...@gmail.com> wrote:
> Hi guys,
> here is an example of what I'm trying to do and I need an idea how it
> can be achieved.
>
> Java code:
> @Entity class Person {
>
>  @Embedded private List<Address> addresses; // I want them to be
> stored as a part of Person, not in a separate collection
> }
>
> @Entity class Address {
>   private String city;
>   private String street;
> }
>
> Mongodb document ( db.Person.find() )
> { _id:"1",
>  addresses: [
>    { _id:"11", city: "Los Angeles", street: "Ohio st"},
>    { _id:"22", city: "San Francisco", street: "Turk st"}
>  ]
> }

There are no _id field in embedded elements.

>
> I want to delete one 'address' from the Person. The mongodb query
> works fine:
> db.Person.update({_id:"1"}, {$pull:{addresses:{_id:"22"}}}). So I need
> to remove an array element based on a value of its property (not its
> position in the array).
> How I can do same thing with Morphia API?

You can remove the array element by value using the update operations.
ops = datastore.createUpdateOperations(Person.class).removeAll("addresses",
new Address("Los Angeles"));

http://code.google.com/p/morphia/wiki/Updating#removeFirst/Last/All

> Thanks.

daen

unread,
Mar 1, 2011, 8:45:32 PM3/1/11
to Morphia
Thanks Scott.

Technically the embedded object can have an ID: you just assign it
manually. I'm assigning UUID to Address in @PrePersist handler. My
goal is to have a simple criteria of getting embedded object. It's
easier to get an embedded thing by its ID instead of using compound
key. Is it incorrect pattern for arrays?


On Mar 1, 5:22 pm, Scott Hernandez <scotthernan...@gmail.com> wrote:

Scott Hernandez

unread,
Mar 1, 2011, 8:51:06 PM3/1/11
to mor...@googlegroups.com, daen
Yes, they can have id fields but I would suggest against using "_id"
except at the root entity (doc). It is confusing and implies it is
like the special, and required, _id field; some people also expect it
to be generated for you -- which won't happen.

It is up to you if you want to assign a unique id for each array
element. It totally depends on the data and access you are using in
the embedded array (element).

daen

unread,
Mar 1, 2011, 9:03:00 PM3/1/11
to Morphia
Right, it makes sense.

So, following your advise, the java code should look like:

@Entity class Person {
@Id private String id;
@Embedded private List<Address> addresses;
}

@Embedded class Address {
private String addressId;
private String city;
private String street;
}

Did I get you idea?

Scott Hernandez

unread,
Mar 1, 2011, 9:05:38 PM3/1/11
to mor...@googlegroups.com, daen
On Tue, Mar 1, 2011 at 6:03 PM, daen <d.ser...@gmail.com> wrote:
> Right, it makes sense.
>
> So, following your advise, the java code should look like:
>
> @Entity class Person {
>   @Id private String id;
>   @Embedded private List<Address> addresses;
> }
>
> @Embedded class Address {
>   private String addressId;
>   private String city;
>   private String street;
> }

You can even do this if you want to and then you won't need @PrePersist.

class Address {
  private ObjectId id = new ObjectId();


  private String city;
  private String street;
}


>
> Did I get you idea?

Yep; I think so.

daen

unread,
Mar 2, 2011, 2:09:11 AM3/2/11
to Morphia
I found an issue with removeAll call. I'm doing remove like in your
example:
datastore.createUpdateOperations(Person.class).removeAll("addresses",
new Address("Los Angeles")); and during this call @PrePersist
annotated method in Address is called. Is it designed behavior?

Thanks.

On Mar 1, 6:05 pm, Scott Hernandez <scotthernan...@gmail.com> wrote:

Scott Hernandez

unread,
Mar 2, 2011, 10:17:41 AM3/2/11
to mor...@googlegroups.com
There could be some argument against that. Depending on what you are
using @PrePersist for you may or may not want it run. It might require
another method or flag to allow users to distinguish between the
states.

This issue is somewhat related:
http://code.google.com/p/morphia/issues/detail?id=179

Uwe Schäfer

unread,
Mar 2, 2011, 10:39:17 AM3/2/11
to mor...@googlegroups.com
On 03/02/2011 04:17 PM, Scott Hernandez wrote:

> There could be some argument against that. Depending on what you are
> using @PrePersist for you may or may not want it run.

sound pretty unintuitive. would you care to give an example?

cu uwe

fredrikbromee

unread,
Mar 2, 2011, 11:37:40 AM3/2/11
to Morphia
Hi guys, sorry for hi-jacking this thread, but our use case is very
similar to daen's and we are using the approach mentioned by Scott.
The problem is nothing is removed since a className property is added
to the pull update request. The embedded object is saved by morphia
without the className property so the update is a no-op. Is there a
way to hint to the update ops that it should NOT use the className
property?

An example of what's in the db:
db.users.find({"_id":"555124116"}, {"fb.tokens":
1})
{ "_id" : "555124116", "fb" : { "tokens" : [
{
"token" : "a long token",
"fetched" : "2011-03-02T11:49:59+01:00"
}
] } }

And if I try to remove that token, morphia generates this update
query:

query: { _id: "555124116" } update: { $pull: { fb.tokens: { className:
"pr.scrap.model.OAuthToken", token: "a long token" } } }

which doesn't match any documents since they don't have the className
property.

We're on the 1.00 snapshot.

Any ideas? Thanks in advance, /Fredrik

On Mar 2, 2:22 am, Scott Hernandez <scotthernan...@gmail.com> wrote:

daen

unread,
Mar 2, 2011, 11:47:17 AM3/2/11
to Morphia
The issue you posted is the same I experience. Just for curiosity: do
you know cases when executing @PrePersist query params is desired?

fredrikbromee

unread,
Mar 2, 2011, 11:51:36 AM3/2/11
to Morphia
No, I can't say I do. But I don't use PrePersist at all really.

daen

unread,
Mar 2, 2011, 11:31:37 PM3/2/11
to Morphia
That question was for Scott :) Sorry.

Alexander Vasiljev

unread,
Mar 4, 2011, 2:53:34 AM3/4/11
to Morphia
Hi! Should we create an issue in the project tracker for this?

Alexander Vasiljev

unread,
Mar 4, 2011, 4:49:17 AM3/4/11
to Morphia
The workaround is to add @Entity(noClassnameStored = true) on the top
of $pull-ing class
Only removeAll ($pull) and hasThisElement ($elemMatch) are affected by
adding className field to the query when update value is an object.
Set or unset are not affected at all.
Reply all
Reply to author
Forward
0 new messages