I am trying to understand why I get the error with some GET calls and not others with a Google App Engine Entity.
The class
@Entity
public class Client {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Key id;
private String firstName;
private String lastName;
@Basic(fetch = FetchType.EAGER)
@ElementCollection
private List<Assessment> assessment;
public List<Assessment> getAssessment() {
return assessment;
}
public void setAssessment(List<Assessment> assessment) {
this.assessment = assessment;
}
}
Works
And if I use a list
request, It works fine.
GET http://localhost:8888/_ah/api/clientendpoint/v1/client
The result is as expected
{
"items": [
{
"id": {
"kind": "Client",
"appId": "no_app_id",
"id": "12",
"complete": true
},
"firstName": "Jane",
"lastName": "Doe"
},
{
"id": {
"kind": "Client",
"appId": "no_app_id",
"id": "13",
"complete": true
},
"firstName": "Jon",
"lastName": "Doe",
}
]
}
Doesn't work
But if I get just 1 record, such as select id 12, an error is produced
GET http://localhost:8888/_ah/api/clientendpoint/v1/client/12
com.google.appengine.repackaged.org.codehaus.jackson.map.JsonMappingException: You have just attempted to access field \"assessment\" yet this field was not detached when you detached the object. Either dont access this field, or detach it when detaching the object. (through reference chain: com.google.api.server.spi.response.CollectionResponse[\"items\"]->com.google.appengine.datanucleus.query.StreamingQueryResult[0]->com.my.app.client.Client[\"assessment\"])
Works if I remove the get / set methods
Where I'm confused is if I comment out the setAssessment(...)
and getAssesment()
methods inside the Client
object, it works fine.
@Entity
public class Client {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Key id;
private String firstName;
private String lastName;
@Basic(fetch = FetchType.EAGER)
@ElementCollection
private List<Assessment> assessment;
//public List<Assessment> getAssessment() {
// return assessment;
//}
//public void setAssessment(List<Assessment> assessment) {
// this.assessment = assessment;
//}
}
It works fine
GET http://localhost:8888/_ah/api/clientendpoint/v1/client/12
Result
{
"id": {
"kind": "Client",
"appId": "no_app_id",
"id": "12",
"complete": true
},
"firstName": "Jane",
"lastName": "Doe"
}
My questions are
Relevant Endpoint code
/**
*
* This method works fine
*
*/
@SuppressWarnings({ "unchecked", "unused" })
@ApiMethod(name = "listClient")
public CollectionResponse<Client> listClient(
@Nullable @Named("cursor") String cursorString,
@Nullable @Named("limit") Integer limit) {
EntityManager mgr = null;
Cursor cursor = null;
List<Client> execute = null;
try {
mgr = getEntityManager();
Query query = mgr.createQuery("select from Client as Client");
if (cursorString != null && cursorString != "") {
cursor = Cursor.fromWebSafeString(cursorString);
query.setHint(JPACursorHelper.CURSOR_HINT, cursor);
}
if (limit != null) {
query.setFirstResult(0);
query.setMaxResults(limit);
}
execute = (List<Client>) query.getResultList();
cursor = JPACursorHelper.getCursor(execute);
if (cursor != null)
cursorString = cursor.toWebSafeString();
} finally {
mgr.close();
}
return CollectionResponse.<Client> builder().setItems(execute)
.setNextPageToken(cursorString).build();
}
/**
*
* This method errors out
*
*/
@ApiMethod(name = "getClient")
public Client getClient(@Named("id") Long id) {
EntityManager mgr = getEntityManager();
Client client = null;
client = mgr.find(Client.class, id);
mgr.close();
return client;
}
I encountered the same problem, and it's definitely a lazy-loading issue:
@ApiMethod(name = "getClient")
public Client getClient(@Named("id") Long id) {
EntityManager mgr = getEntityManager();
Client client = mgr.find(Client.class, id);
if (client != null)
client.getAssessment(); // Forces it to load your objects
mgr.close();
return client;
}
Unfortunately, I couldn't find a cleaner way to fix this, so please let me know if you have better alternatives! One final thing: I also would love to know why it behaves differently when Kirk comments out the getters/setters.
EDIT 26/12/2013:
It seems like fetch types are now properly working using the latest version of App Engine + Datanucleus. I also found a very interesting behaviour:
TypedQuery<T>
instead of Query
, JPA will try to load all of T's fields (it ignores FetchType.LAZY
), and you will get a JsonMappingException if those fields weren't manually loaded.Query
, JPA will only load the fields marked as FetchType.EAGER
, so watch out for exceptions if you intend to use fields that were marked as lazy but weren't properly loaded.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With