这个问题已经被问了很多次,但我仍然找不到适合我的用例的解决方案:
-
我有一个使用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的等价物。以下是我尝试过的事情(主要基于如何解决"未能延迟初始化角色集合"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根本不进行"联接"。
相反:
- 它执行一个"选择"来获取父对象
- 如果你试图从任何一个孩子身上读一些东西,那么它会另一个"选择",以获取孩子。一个"选择"给父母,另一个给孩子
- 如果实体被配置为"紧急获取",它总是提前完成所有选择
- 对于"懒惰获取",在关闭会话之前,必须尝试读取子项(从而触发第二次选择)。否则,如果您试图在会话关闭后读取子数据,则会得到"未能延迟初始化集合">
- Left Join在这种情况下不起作用:数据库返回的结果集与实体不匹配。为了使它发挥作用,我必须一次读取结果集一行,然后手动构建实体
- 我仍在寻找一种方法,让"加入获取"在我的场景中发挥作用
如果您在实体配置中使用Lazy Initialization进行OneToMany映射,并且在某些情况下,您希望急切地获取集合。我认为您可以使用@NamedQuery在单个查询中获取List(在您的案例中为Left Join)。
甚至在我发布问题之前,我就已经处理过这个SO线程:
如何解决"未能延迟初始化角色集合"Hibernate异常
问题是由使用hibernate访问属性引起的会话结束。中没有休眠事务控制器。
可能的解决方案:
在服务层执行所有这些逻辑(使用@Transactional),而不是在控制器中。应该有合适的地方来做这件事,它是应用程序的逻辑的一部分而不是在控制器中(在这种情况下,加载模型的接口)。服务中的所有操作层应该是事务性的。。。
使用"热切"而不是"懒惰">。现在您没有使用"懒惰"。。是的不是一个真正的解决方案,如果你想使用lazy,它就像一个临时的(非常临时的)变通方法。
在控制器中使用@Transactional。它不应该在这里使用,你将服务层和演示混合在一起,这不是一个好的设计。
- 使用OpenSessionInViewFilter,报告了许多缺点,可能不稳定
不幸的是,@Transactional
和OpenSessionInView
在我的场景中不可用。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调用。
不管怎样,我已经成功了。