繁体   English   中英

Hibernate“无法延迟初始化集合”运行时错误

[英]Hibernate "failed to lazily initialize a collection" runtime error

这个问题已经被问过很多次了,但我仍然找不到适合我的用例的解决方案:

  1. 我有一个使用 Hibernate 5.x 的 Struts2 应用程序。

  2. 该应用程序是一个“联系人应用程序”。 它有两个实体:一个“联系人”,可以有零个或多个“注释”。

  3. 这是我获取联系人的方式:

     @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; ...
  4. 它工作得很好。 直到我尝试这样的事情:

     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/:?]
    ...

可能的解决方案

  1. 不想改变FetchType.Eager

  2. 因为我没有使用 Spring,所以我不能使用@TransactionalOpenSessionInView 但如果有 Hibernate-only 的等价物,我会很高兴。

  3. 这些是我尝试过的事情(主要基于如何解决“无法延迟初始化角色集合”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 根本不做“加入”。

反而:

  1. 它执行“选择”以获取父级。
  2. 如果您尝试从任何孩子那里读取某些内容,那么它会执行另一个“选择”以获取孩子。 一个“选择”给父母,第二个给孩子。
  3. 如果实体被配置为“eager fetch”,它总是预先进行所有选择。
  4. 对于“延迟获取”,您必须在关闭会话之前尝试读取子项(从而触发第二个选择)。 否则,如果您在会话关闭后尝试读取子数据,则会“无法延迟初始化集合”。
  5. 左连接在这种情况下不起作用:数据库返回的结果集与实体不匹配。 为了使其工作,我必须一次读取一行结果集,并手动构建实体。
  6. 我仍在寻找一种方法让“加入提取”在我的场景中工作......

如果您在实体配置中使用延迟初始化进行 OneToMany 映射,并且在某些情况下您想急切地获取集合。 我认为您可以使用 @NamedQuery 在单个查询中获取列表(在您的情况下为 Left Join)。

甚至在我发布我的问题之前,我就使用了这个 SO 线程:

如何解决Hibernate异常“无法延迟初始化角色集合”

该问题是由于在休眠会话关闭的情况下访问属性引起的。 您在控制器中没有休眠事务。

可能的解决方案:

  • 在服务层(使用@Transactional)而不是在控制器中执行所有这些逻辑 应该有合适的地方来做这件事,它是应用程序逻辑的一部分,而不是在控制器中(在这种情况下,是一个加载模型的界面)。 服务层的所有操作都应该是事务性的......

  • 使用 'eager' 而不是 'lazy' 现在你没有使用 'lazy' .. 这不是一个真正的解决方案,如果你想使用 lazy,就像一个临时(非常临时)的解决方法。

  • 在 Controller 中使用 @Transactional 它不应该在这里使用,您将服务层与表示混合在一起,这不是一个好的设计。

  • 使用 OpenSessionInViewFilter ,报告了许多缺点,可能不稳定。

不幸的是,@ @TransactionalOpenSessionInView在我的场景中不可用。 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM