[英]Spring MVC + Hibernate: could not initialize proxy - no Session
Note: See my own answer to this question for an example of how I solved this issue. 注意:请参阅我自己对此问题的回答,以获取我如何解决此问题的示例。
I am getting the following exception in my Spring MVC 4 + Hibernate 4 project: 我在Spring MVC 4 + Hibernate 4项目中遇到以下异常:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.mysite.Company.acknowledgements, could not initialize proxy - no Session
org.hibernate.LazyInitializationException:懒得初始化一个角色集合:com.mysite.Company.acknowledgements,无法初始化代理 - 没有Session
After reading a lot of other questions about this issue, I understand why this exception occurs, but I am not sure how to fix it in a good way. 在阅读了很多关于这个问题的其他问题后,我理解为什么会发生这种异常,但我不确定如何以一种好的方式解决它。 I am doing the following:
我正在做以下事情:
I have previously worked with PHP and doctrine2, and this way of doing things caused no problems. 我之前使用过PHP和doctrine2,这种做事方式没有问题。 I am trying to figure out the best way to solve this, because the solutions that I found so far don't seem so great:
我试图找出解决这个问题的最佳方法,因为到目前为止我找到的解决方案看起来并不那么好:
Hibernate.initialize(myObject.getAssociation());
Hibernate.initialize(myObject.getAssociation());
- this means that I have to loop through associations to initialize them (I guess), and just the fact that I have to do this, kind of makes the lazy loading less neat I tried to use @Transactional
in my service, but without luck. 我尝试在我的服务中使用
@Transactional
,但没有运气。 This makes sense because I am trying to access data that has not yet been loaded after my service method returns. 这是有道理的,因为我试图访问我的服务方法返回后尚未加载的数据。 Ideally I would like to be able to access any association from within my view.
理想情况下,我希望能够从我的视图中访问任何关联。 I guess the drawback of initializing the associations within my service is that I have to explicitly define which data I need - but this depends on the context (controller) in which the service is used.
我想在我的服务中初始化关联的缺点是我必须明确定义我需要的数据 - 但这取决于使用服务的上下文(控制器)。 I am not sure if I can do this within my controller instead without losing the abstraction that the DBAL layer provides.
我不确定我是否可以在我的控制器中执行此操作而不会丢失DBAL层提供的抽象。 I hope that makes sense.
我希望这是有道理的。 Either way, it would be great if I didn't have to always explicitly define which data I want to be available to my view, but just let the view do its thing.
无论哪种方式,如果我不必总是明确定义我想要哪些数据可用于我的视图,那就太好了,但只是让视图做它的事情。 If that is not possible, then I am just looking for the most elegant solution to the problem.
如果那是不可能的,那么我只是在寻找最优雅的解决方案。
Below is my code. 以下是我的代码。
View 视图
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<h1><c:out value="${company.name}" /> (ID: <c:out value="${company.id}" />)</h1>
<c:forEach var="acknowledgement" items="${company.acknowledgements}">
<p><c:out value="${acknowledgement.name}" /></p>
</c:forEach>
Controller 调节器
@Controller
public class ProfileController {
@Autowired
private CompanyService companyService;
@RequestMapping("/profile/view/{id}")
public String view(Model model, @PathVariable int id) {
Company company = this.companyService.get(id);
model.addAttribute("company", company);
return "viewCompanyProfile";
}
}
Service 服务
@Service
public class CompanyServiceImpl implements CompanyService {
@Autowired
private CompanyDao companyDao;
@Override
public Company get(int id) {
return this.companyDao.get(id);
}
}
DAO DAO
@Repository
@Transactional
public class CompanyDaoImpl implements CompanyDao {
@Autowired
private SessionFactory sessionFactory;
@Override
public Company get(int id) {
return (Company) this.sessionFactory.getCurrentSession().get(Company.class, id);
}
}
Company entity 公司实体
@Entity
@Table(name = "company")
public class Company {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
// Other fields here
@ManyToMany
@JoinTable(name = "company_acknowledgement", joinColumns = @JoinColumn(name = "company_id"), inverseJoinColumns = @JoinColumn(name = "acknowledgement_id"))
private Set<Acknowledgement> acknowledgements;
public Set<Acknowledgement> getAcknowledgements() {
return acknowledgements;
}
public void setAcknowledgements(Set<Acknowledgement> acknowledgements) {
this.acknowledgements = acknowledgements;
}
// Other getters and setters here
}
Acknowledgement entity 确认实体
@Entity
public class Acknowledgement {
@Id
private int id;
// Other fields + getters and setters here
}
mvc-dispatcher-servlet.xml (a part of it) mvc-dispatcher-servlet.xml(它的一部分)
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="packagesToScan">
<list>
<value>com.mysite.company.entity</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQL9Dialect</prop>
</props>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven />
Thanks in advance! 提前致谢!
As @ Predrag Maric said writing service methods with different initialization for entity assotiations might be an option. 正如@ Predrag Maric所说,编写具有不同初始化实体关联的服务方法可能是一种选择。
OpenSessionInView is a quit discussable pattern, which can be a solution in your case, though. OpenSessionInView是一个退出讨论模式,但在您的情况下可以是一个解决方案。
Another option is to set 另一个选择是设置
<prop key="hibernate.enable_lazy_load_no_trans">true</prop>
property. 属性。 It is designed to solve
org.hibernate.LazyInitializationException
problem and is available since hibernate 4.1.6. 它旨在解决
org.hibernate.LazyInitializationException
问题,并且自hibernate 4.1.6起可用。
So what does this property do?
那么这个属性有什么作用呢? It simply signals Hibernate that it should open a new session if session which is set inside current un-initialised proxy is closed.
它只是告诉Hibernate如果在当前未初始化的代理中设置的会话关闭,它应该打开一个新的会话。 You should be aware that if you have any other open session which is also used to manage current transaction, this newly opened session will be different and it may not participate into the current transaction unless it is JTA.
您应该知道,如果您还有用于管理当前事务的任何其他打开会话,则此新打开的会话将不同,并且除非是JTA,否则它可能不会参与当前事务。 Because of this TX side effect you should be careful against possible side effects in your system.
由于这种TX副作用,您应该小心系统中可能存在的副作用。
Find more information here: http://blog.harezmi.com.tr/hibernates-new-feature-for-overcoming-frustrating-lazyinitializationexceptions and Solve Hibernate Lazy-Init issue with hibernate.enable_lazy_load_no_trans 在此处查找更多信息: http : //blog.harezmi.com.tr/hibernates-new-feature-for-overcoming-frustrating-lazyinitializationexceptions并使用hibernate.enable_lazy_load_no_trans解决Hibernate Lazy-Init问题
The simplest and most transparent solution is the OSIV pattern. 最简单,最透明的解决方案是OSIV模式。 As I'm sure you aware, there is plenty of discussion about this (anti)pattern and the alternatives both on this site and elsewhere so no need to go over that again.
我相信你已经知道,关于这个(反)模式以及这个网站和其他地方的替代方案都有很多讨论,所以不需要再重复一遍。 For example:
例如:
Why is Hibernate Open Session in View considered a bad practice? 为什么Hibernate Open Session in View被认为是一种不好的做法?
However, in my opinion, not all criticisms of OSIV are entirely accurate (eg the transaction will not commit till your view is rendered? Really? If you are using the Spring implementation then https://stackoverflow.com/a/10664815/1356423 ) 但是,在我看来,并非所有对OSIV的批评都是完全准确的(例如,在您的视图呈现之前事务不会提交?真的吗?如果您使用的是Spring实现,那么https://stackoverflow.com/a/10664815/1356423 )
Also, be aware that JPA 2.1 introduced the concept of Fetch Graphs which give you more control over what is loaded. 另外,请注意JPA 2.1引入了Fetch Graphs的概念,它可以让您更好地控制加载的内容。 I haven't tried it yet but maybe this if finally a solution for this long standing problem!
我还没有尝试过,但也许这可能是最终解决这个长期存在的问题!
http://www.thoughts-on-java.org/2014/03/jpa-21-entity-graph-part-1-named-entity.html http://www.thoughts-on-java.org/2014/03/jpa-21-entity-graph-part-1-named-entity.html
My vote goes to Hibernate.initialize(myObject.getAssociation())
in service layer (which also means @Transactional
should be moved from DAO to service methods) 我的投票转到服务层中的
Hibernate.initialize(myObject.getAssociation())
(这也意味着@Transactional
应该从DAO转移到服务方法)
IMHO, service methods should return all data that is required by the caller. 恕我直言,服务方法应该返回调用者所需的所有数据。 If the caller wants to display some association on the view, it is service's responsibility to supply that data.
如果调用者想要在视图上显示某些关联,则服务负责提供该数据。 This means you could have several methods that apparently do the same thing (return
Company
instance), but depending on the caller, different associations could be fetched. 这意味着你可以有几个方法显然做同样的事情(返回
Company
实例),但根据调用者,可以获取不同的关联。
On one project we had kind of configuration class, which contained information on what associations should be fetched, and we had a single service method which also accepted that class as a parameter. 在一个项目中,我们有一种配置类,其中包含应该获取哪些关联的信息,并且我们有一个服务方法,它也接受该类作为参数。 This approach meant we have only one method which is flexible enough to support all callers.
这种方法意味着我们只有一种方法足够灵活,可以支持所有呼叫者。
@Override
public Company get(int id, FetchConfig fc) {
Company result = this.companyDao.get(id);
if (fc.isFetchAssociation1()) {
Hibernate.initialize(result.getAssociation1());
}
...
return result;
}
For others who are looking for a solution, here is how I solved the problem. 对于正在寻找解决方案的其他人来说,这就是我解决问题的方法。
As Alan Hay pointed out, JPA 2.1+ supports entity graphs, which ended up solving my problem. 正如Alan Hay指出的那样,JPA 2.1+支持实体图,最终解决了我的问题。 To make use of it, I change my project to use the
javax.persistence.EntityManager
class instead of the SessionFactory
, which would then hand me the current session. 为了使用它,我将我的项目更改为使用
javax.persistence.EntityManager
类而不是SessionFactory
,然后会将当前会话交给我。 Here is how I configured it in my dispatcher servlet configuration (some things excluded): 以下是我在调度程序servlet配置中配置它的方法(排除了一些事项):
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd">
<jee:jndi-lookup id="dataSource" jndi-name="java:/comp/env/jdbc/postgres" expected-type="javax.sql.DataSource"/>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="packagesToScan" value="dk.better.company.entity, dk.better.user.entity" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
</property>
<property name="jpaProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQL9Dialect</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<tx:annotation-driven />
</beans>
Below is an example of a DAO class. 下面是DAO类的示例。
@Transactional
public class CompanyDaoImpl implements CompanyDao {
@PersistenceContext
private EntityManager entityManager;
@Override
public Company get(int id) {
EntityGraph<Company> entityGraph = this.entityManager.createEntityGraph(Company.class);
entityGraph.addAttributeNodes("acknowledgements");
Map<String, Object> hints = new HashMap<String, Object>();
hints.put("javax.persistence.loadgraph", entityGraph);
return this.entityManager.find(Company.class, id, hints);
}
}
I found that if I did not use @PersistenceContext
but Autowired
instead, then the database session was not closed when rendering the view, and data updates were not reflected in subsequent queries. 我发现如果我没有使用
@PersistenceContext
而是使用Autowired
,那么在渲染视图时数据库会话没有关闭,并且数据更新没有反映在后续查询中。
For more information on entity graphs, I encourage you to read the following articles: 有关实体图的更多信息,建议您阅读以下文章:
http://www.thoughts-on-java.org/2014/03/jpa-21-entity-graph-part-1-named-entity.html http://www.thoughts-on-java.org/2014/03/jpa-21-entity-graph-part-1-named-entity.html
http://www.thoughts-on-java.org/2014/04/jpa-21-entity-graph-part-2-define.html http://www.thoughts-on-java.org/2014/04/jpa-21-entity-graph-part-2-define.html
Trigger in the service layer requires lazy loading of the Set's size method. 服务层中的触发器需要延迟加载Set的size方法。
Tools: 工具:
public class HibernateUtil {
/**
* Lazy = true when the trigger size method is equal to lazy = false (load all attached)
*/
public static void triggerSize(Collection collection) {
if (collection != null) {
collection.size();
}
}
}
in your service method: 在您的服务方法中:
Apple apple = appleDao.findById('xxx');
HibernateUtil.triggerSize(apple.getColorSet());
return apple;
then use apple
in controller, everything is ok! 然后在控制器中使用
apple
,一切都好!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.