This question has been asked many times, but I still can't find a solution for my use case:
I have a Struts2 application using Hibernate 5.x.
The application is a "Contacts app". It has two entities: a "Contact", which can have zero or more "Notes".
Here's how I get the contacts:
@Override public List<Contact> getContacts() { //Note: Hibernate 5++ supports Java try-with-resource blocks try (Session session = HibernateUtil.openSession()) { List<Contact> contacts = session.createQuery("FROM Contact").list(); return contacts; ...
It works great. Until I try something like this:
ObjectMapper mapper = new ObjectMapper(); jsonString = mapper.writeValueAsString(contacts);
ERROR:
16:05:16.174 [http-nio-8080-exec-2] DEBUG org.apache.struts2.dispatcher.Dispatcher - Dispatcher serviceAction failed
com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: com.example.contactsapp.models.Contact.notes, could not initialize proxy - no Session (through reference chain: java.util.ArrayList[0]->com.example.contactsapp.models.Contact["notes"])
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:394) ~[jackson-databind-2.10.0.jar:2.10.0]
...
at com.fasterxml.jackson.databind.ObjectMapper._configAndWriteValue(ObjectMapper.java:4094) ~[jackson-databind-2.10.0.jar:2.10.0]
at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString(ObjectMapper.java:3404) ~[jackson-databind-2.10.0.jar:2.10.0]
at com.example.contactsapp.actions.ContactsAction.getContacts(ContactsAction.java:36) ~[classes/:?]
...
POSSIBLE SOLUTIONS
I do NOT want to change to
FetchType.Eager
.Since I'm not using Spring, I can't use
@Transactional
orOpenSessionInView
. But I'd love if there were equivalents for Hibernate-only.These are the things I've tried (based largely on How to solve the “failed to lazily initialize a collection of role” Hibernate exception):
@Override public List<Contact> getContactsFetchAll() { //Note: Hibernate 5++ supports Java try-with-resource blocks try (Session session = HibernateUtil.openSession()) { // Jackson mapper.writeValueAsString() => "failed to lazily initialize a collection" // List<Contact> contacts = session.createQuery("FROM Contact").list(); // Plan A: Causes same "failed to lazily initialize a collection" runtime error // List<Contact> contacts = session.createQuery("FROM Contact").list(); // Hibernate.initialize(contacts); // Plan B: Still no-go: returns [] empty set // List<Contact> contacts = session.createQuery("SELECT c FROM Contact c JOIN FETCH c.notes n").list(); // Plan C: Same: "failed to lazily initialize a collection of role..." // Query query = session.createQuery("FROM Contact"); // Hibernate.initialize(query); // List<Contact> contacts = query.list(); // Plan D: Same: "ERROR: failed to lazily initialize a collection of role" // List<Contact> contacts = session.createQuery("FROM Contact").list(); // for (Contact c : contacts) { // Set<Note> n = c.getNotes(); // } return contacts; } }
Q: Any suggestions?
Q: Do you need any additional information?
I've done a bit more research.
Apparently, in this particular scenario, Hibernate doesn't do a "join" at all.
Instead:
- It does a "select" to get the parent.
- If you try to read something from any of the children, then it does ANOTHER "select", to get the children. One "select" for the parent, a second for the children.
- If the entity is configured for "eager fetch", it always does all selects up-front.
- For "lazy fetch", you MUST try to read the children (thus triggering the second select) before closing the session. Otherwise, if you try to read the child data after the session closes, you get "failed to lazily initialize a collection".
- A Left Join doesn't work in this scenario: the result set returned by the database simply doesn't match the entity. To make it work, I'd have to read the result set a row at a time, and build the entity manually.
- I'm still looking for a way to get "Join Fetch" to work in my scenario...