Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding a JsonMappingException with Google Endpoint

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

  • Why does it work when I comment out the get/set
  • How do I properly fix this?
  • I'm having trouble finding good examples of code for Java App Engine code, other than the simple examples in the docs. Anyone know of any good examples?

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;
}
like image 244
Kirk Avatar asked Oct 04 '22 21:10

Kirk


1 Answers

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:

  • If you use 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.
  • If you use 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.
like image 188
elyas-bhy Avatar answered Oct 07 '22 20:10

elyas-bhy