休眠"failed to lazily initialize a collection"运行时错误



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

  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的等价物。

  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;
    }
    }
    

Q:有什么建议吗?

Q: 你需要其他信息吗?


我做了更多的研究。

显然,在这个特定的场景中,Hibernate根本不进行"联接"。

相反:

  1. 它执行一个"选择"来获取父对象
  2. 如果你试图从任何一个孩子身上读一些东西,那么它会另一个"选择",以获取孩子。一个"选择"给父母,另一个给孩子
  3. 如果实体被配置为"紧急获取",它总是提前完成所有选择
  4. 对于"懒惰获取",在关闭会话之前,必须尝试读取子项(从而触发第二次选择)。否则,如果您试图在会话关闭后读取子数据,则会得到"未能延迟初始化集合">
  5. Left Join在这种情况下不起作用:数据库返回的结果集与实体不匹配。为了使它发挥作用,我必须一次读取结果集一行,然后手动构建实体
  6. 我仍在寻找一种方法,让"加入获取"在我的场景中发挥作用

如果您在实体配置中使用Lazy Initialization进行OneToMany映射,并且在某些情况下,您希望急切地获取集合。我认为您可以使用@NamedQuery在单个查询中获取List(在您的案例中为Left Join)。

甚至在我发布问题之前,我就已经处理过这个SO线程:

如何解决"未能延迟初始化角色集合"Hibernate异常

问题是由使用hibernate访问属性引起的会话结束。中没有休眠事务控制器。

可能的解决方案:

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

  • 使用"热切"而不是"懒惰">。现在您没有使用"懒惰"。。是的不是一个真正的解决方案,如果你想使用lazy,它就像一个临时的(非常临时的)变通方法。

  • 在控制器中使用@Transactional。它不应该在这里使用,你将服务层和演示混合在一起,这不是一个好的设计。

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

不幸的是,@TransactionalOpenSessionInView在我的场景中不可用。FetchType.EAGER不是一个选项。"在会议中做所有事情"正是我试图解决的问题。

来自同一线程的另一个出色响应:

根据我的经验,我有以下方法来解决著名的惰性初始化异常:

  • 使用休眠。初始化

    Hibernate.initialize(topics.getComments());

  • 使用JOIN FETCH

您可以在JPQL中使用JOINFETCH语法来显式获取儿童收集出来。这有点像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个子Notes的联系人。

    解决方法:更改代码以确保每个联系人至少有一个备注(通过在创建联系人时自动创建备注)。"Join fetch"只进行一次SQL调用。

不管怎样,我已经成功了。

相关内容

最新更新