问题
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?
回答1:
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:
- Add
@Load
to the user property, so theAccount
object is fetched efficiently. - Make your
@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.
来源:https://stackoverflow.com/questions/24071134/objectify-loads-object-behind-ref-even-when-load-is-not-specified