I have an account object which references a user object.
@Cache
@Entity
public final class Account {
@Id Long id;
@Index private Ref<User> user;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public User getUser() {
return user.get();
}
public void setUser(User user) {
this.user = Ref.create(user);
}
}
I have hidden the Ref as recommended here: http://code.google.com/p/objectify-appengine/wiki/Entities - please note the Ref does not have the @Load annotation.
When I call my Google Cloud Endpoint from an Android client, it looks like Objectify delivers the account object with the embedded user, even though @Load is not specified.
@ApiMethod(name = "account.get")
public Account getAccount(
@Named("id") final Long id
) {
return ofy().load().type(Account.class).id(id).now();
}
When I query the account directly using Apis Explorer, I also get both, account with the user embedded:
200 OK
{
"id": "5079604133888000",
"user": { "id": "5723348596162560",
"version": "1402003195251",
"firstName": "Karl" },
"kind": "api#accountItem",
"etag": "\"30khohwUBSGWr00rYOZuF9f4BTE/Q31EvnQCQ6E9c5YXKEZHNsD_mlQ\""}
This raises three questions:
- Does Appengine always return embedded Refs natively and does Objectify always pass on objects which it already knows?
- What exactly is @Load for and is there a way to control this behavior? Load Groups?
- Have I missed something? Why isn't @Load obeyed?
In your example code, you are not specifying
@Load
which means that loading the account will not fetch theUser
. However, your@ApiMethod
is serializing the account back to the client, so theuser
property is been accessed, thus a separate fetch is issued to load the user object. That's why you are getting the information of the user when calling the method.Not specifying
@Load
doesn't mean that you won't get aUser
back. It means that you are not going to retrieve aUser
unless you specifically ask for it later.Ref works like this:
@Load
myself, then I will fetch the data initially and have it ready for you.So this is working fine in your code... but then your
@ApiMethod
is serializing yourAccount
object back to the client. The serialization process is going through every property in yourAccount
object, including theuser
property. At this point, theRef<User>
is being accessed, so the data will get fetched from the Datastore and then returned to the client.This is making your code very inefficient, since the
Account
objects are loaded without theUser
information, but then you always access theUser
info later (during serialization), issuing a separate fetch. Batchinggets
from the Datastore is way more efficient than issuing separategets
.In your case, you can do either of two things:
@Load
to the user property, so theAccount
object is fetched efficiently.@ApiMethod
return a differentAccount
object without theuser
property (thus avoiding fetching the user if you don't need it).Option 2 above is quite useful since you can abstract your internal Datastore structure from what the client sees. You'll find yourself using this patter quite often.