[英]Hibernate "failed to lazily initialize a collection" runtime error
这个问题已经被问过很多次了,但我仍然找不到适合我的用例的解决方案:
我有一个使用 Hibernate 5.x 的 Struts2 应用程序。
该应用程序是一个“联系人应用程序”。 它有两个实体:一个“联系人”,可以有零个或多个“注释”。
这是我获取联系人的方式:
@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; ...
它工作得很好。 直到我尝试这样的事情:
ObjectMapper mapper = new ObjectMapper(); jsonString = mapper.writeValueAsString(contacts);
错误:
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/:?]
...
可能的解决方案
我不想改变FetchType.Eager
。
因为我没有使用 Spring,所以我不能使用@Transactional
或OpenSessionInView
。 但如果有 Hibernate-only 的等价物,我会很高兴。
这些是我尝试过的事情(主要基于如何解决“无法延迟初始化角色集合”Hibernate 异常):
@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; } }
问:有什么建议吗?
问:您需要任何其他信息吗?
我做了更多的研究。
显然,在这种特殊情况下,Hibernate 根本不做“加入”。
反而:
如果您在实体配置中使用延迟初始化进行 OneToMany 映射,并且在某些情况下您想急切地获取集合。 我认为您可以使用 @NamedQuery 在单个查询中获取列表(在您的情况下为 Left Join)。
甚至在我发布我的问题之前,我就使用了这个 SO 线程:
该问题是由于在休眠会话关闭的情况下访问属性引起的。 您在控制器中没有休眠事务。
可能的解决方案:
在服务层(使用@Transactional)而不是在控制器中执行所有这些逻辑。 应该有合适的地方来做这件事,它是应用程序逻辑的一部分,而不是在控制器中(在这种情况下,是一个加载模型的界面)。 服务层的所有操作都应该是事务性的......
使用 'eager' 而不是 'lazy' 。 现在你没有使用 'lazy' .. 这不是一个真正的解决方案,如果你想使用 lazy,就像一个临时(非常临时)的解决方法。
在 Controller 中使用 @Transactional 。 它不应该在这里使用,您将服务层与表示混合在一起,这不是一个好的设计。
- 使用 OpenSessionInViewFilter ,报告了许多缺点,可能不稳定。
不幸的是,@ @Transactional
和OpenSessionInView
在我的场景中不可用。 FetchType.EAGER
不是一个选项。 而“在会议中做所有事情”正是我试图解决的问题。
来自同一线程的另一个很好的回应:
根据我的经验,我有以下方法来解决著名的 LazyInitializationException:
使用 Hibernate.initialize
Hibernate.initialize(topics.getComments());
- 使用 JOIN FETCH
您可以在 JPQL 中使用 JOIN FETCH 语法来显式提取子集合。 这有点像 EAGER 获取。
- 使用 OpenSessionInViewFilter
我学到的是:
List<Contact> contacts = session.createQuery("FROM Contact").list();
: 只返回“父”(Contact) 记录,不返回子(Notes) 记录。
变通方法:在会话仍处于活动状态时明确阅读每个子笔记。 这会触发第二个“选择”,并获取子数据。
List<Contact> contacts = session.createQuery("SELECT c FROM Contact c INNER JOIN FETCH c.notes").list();
: 不返回任何带有 0 个子笔记的联系人。
变通方法:更改代码以确保每个联系人至少有一个备注(通过在创建联系人时自动创建备注)。 “Join fetch”只进行一次 SQL 调用。
无论如何 - 我让它工作了。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.