Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Objectify loads object behind Ref<?> even when @Load is not specified

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:

  1. Does Appengine always return embedded Refs natively and does Objectify always pass on objects which it already knows?
  2. What exactly is @Load for and is there a way to control this behavior? Load Groups?
  3. Have I missed something? Why isn't @Load obeyed?
like image 563
Oliver Hausler Avatar asked Jun 05 '14 22:06

Oliver Hausler


1 Answers

In your example code, you are not specifying @Load which means that loading the account will not fetch the User. However, your @ApiMethod is serializing the account back to the client, so the user 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 a User back. It means that you are not going to retrieve a User unless you specifically ask for it later.

Ref works like this:

  • I'm a reference, so by default I won't fetch the data.
  • If you ask for me, then I will first load the data, then answer you.
  • Oh, if you tell me to @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 your Account object back to the client. The serialization process is going through every property in your Account object, including the user property. At this point, the Ref<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 the User information, but then you always access the User info later (during serialization), issuing a separate fetch. Batching gets from the Datastore is way more efficient than issuing separate gets.

In your case, you can do either of two things:

  1. Add @Load to the user property, so the Account object is fetched efficiently.
  2. Make your @ApiMethod return a different Account object without the user 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.

like image 63
svpino Avatar answered Nov 15 '22 04:11

svpino