spring JPA 和 Hibernate 中的 LazyInitializationException
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/12428085/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
LazyInitializationException in JPA and Hibernate
提问by Eddie
I know that this question has been ask numerous times here and across the internet and I have read through many of those answers but I still don't understand the proper way to solve this problem. I am experimenting with Spring MVC and JPA and every time I access a lazily loaded property I get a LazyInitializationException.
我知道这个问题在这里和在互联网上被问过很多次,我已经阅读了许多答案,但我仍然不明白解决这个问题的正确方法。我正在试验 Spring MVC 和 JPA,每次访问延迟加载的属性时,我都会收到一个 LazyInitializationException。
Here is some of the code I am experimenting with:
这是我正在试验的一些代码:
@Repository
public class MyDAO {
private static final Logger logger = LoggerFactory.getLogger(MyDAO.class);
@PersistenceContext
private EntityManager em;
@Transactional
public void logDOI() {
DOI myDOI = em.find(DOI.class, Long.valueOf(1));
// This line gives the expected output
logger.info("Fetched DOI: " + myDOI.getDoiKey());
// This line throws the LazyInitalizationException
for(DOIMembership m : myDOI.getDoiMemberships()) {
logger.info("Got DOI Membership id: " + m.getId());
}
}
}
The entity I am accessing:
我正在访问的实体:
@Entity
@Table(name="DOI")
public class DOI implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@SequenceGenerator(name="DOI_ID_GENERATOR", sequenceName="DOI_SEQ")
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="DOI_ID_GENERATOR")
private long id;
// Other properties omitted
//bi-directional many-to-one association to DOIMembership
@OneToMany(mappedBy="doi", fetch=FetchType.LAZY)
private Set<DOIMembership> doiMemberships;
public DOI() {
}
public long getId() {
return this.id;
}
public void setId(long id) {
this.id = id;
}
// Other Accessors Omitted
}
The entity referenced from DOI
从 DOI 引用的实体
@Entity
@Table(name="DOI_MEMBERSHIP")
public class DOIMembership implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@SequenceGenerator(name="DOI_MEMBERSHIP_ID_GENERATOR", sequenceName="DOI_MEMBERSHIP_SEQ")
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="DOI_MEMBERSHIP_ID_GENERATOR")
private long id;
//bi-directional many-to-one association to DOI
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="DOI_ID")
private DOI doi;
@Column(name="GROUP_ID")
private BigDecimal groupId;
@Column(name="DATA_SET")
private BigDecimal dataSetId;
public DOIMembership() {
}
public BigDecimal getGroupId() {
return groupId;
}
public BigDecimal getDataSetId() {
return dataSetId;
}
public void setDataSetId(BigDecimal dataSetId) {
this.dataSetId = dataSetId;
}
public void setGroupId(BigDecimal groupId) {
this.groupId = groupId;
}
public long getId() {
return this.id;
}
public void setId(long id) {
this.id = id;
}
public DOI getDoi() {
return this.doi;
}
public void setDoi(DOI doi) {
this.doi = doi;
}
}
The Spring MVC Controller:
Spring MVC 控制器:
@Controller
@RequestMapping("/summary")
public class DOISummaryController {
@Autowired
MyDAO myDAO;
@RequestMapping()
public String DOISummary() {
myDAO.logDOI();
return "home";
}
}
My Spring configuration:
我的弹簧配置:
<?xml version="1.0" encoding="UTF-8"?>
<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:task="http://www.springframework.org/schema/task"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<!-- Root Context: defines shared resources visible to all other web components -->
<context:property-placeholder location="WEB-INF/spring/root-context.properties, WEB-INF/spring/datasource-context.properties" />
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName">
<value>oracle.jdbc.driver.OracleDriver</value>
</property>
<property name="url">
<value>${Url}</value>
</property>
<property name="username">
<value>${Username}</value>
</property>
<property name="password">
<value>${Password}</value>
</property>
</bean>
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
</property>
<property name="packagesToScan" value="org.myorg.doi.domain" />
<property name="jpaProperties">
<props>
<prop key="hibernate.dialect">
org.hibernate.dialect.Oracle10gDialect
</prop>
<prop key="hibernate.max_fetch_depth">3</prop>
<prop key="hibernate.jdbc.fetch_size">50</prop>
<prop key="hibernate.jdbc.batch_size">10</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="emf" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
<context:annotation-config />
<task:annotation-driven />
<context:component-scan base-package="org.myorg.doi" />
</beans>
And a stack trace, as requested:
和堆栈跟踪,根据要求:
SEVERE: Servlet.service() for servlet [appServlet] in context with path [/DOI] threw exception [Request processing failed; nested exception is org.hibernate.LazyInitializationException: could not initialize proxy - no Session] with root cause
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:430)
at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:121)
at org.hibernate.collection.internal.PersistentSet.iterator(PersistentSet.java:180)
at org.myorg.doi.dao.MyDAO.logDOI(MyDAO.java:27)
at org.myorg.doi.web.DOISummaryController.DOISummary(DOISummaryController.java:29)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:213)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:126)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:96)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:617)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:578)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:923)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:852)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:882)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:778)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:621)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:722)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:225)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:168)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:98)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:927)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
at com.springsource.insight.collection.tcserver.request.HttpRequestOperationCollectionValve.traceNextValve(HttpRequestOperationCollectionValve.java:116)
at com.springsource.insight.collection.tcserver.request.HttpRequestOperationCollectionValve.invoke(HttpRequestOperationCollectionValve.java:98)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:407)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1001)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:585)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:310)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:680)
I you can see, I am trying to use pure JPA and only using Hibernate as a JPA provider.
您可以看到,我正在尝试使用纯 JPA,并且仅使用 Hibernate 作为 JPA 提供程序。
I understand that the exception is caused by the session being detached from the entity. But, I thought that wouldn't happen if we are currently in a transaction, which should be the case since the logDOI method is annotated with @Transactional.
我知道异常是由会话与实体分离引起的。但是,我认为如果我们当前处于事务中就不会发生这种情况,因为 logDOI 方法是用 @Transactional 注释的。
Of course, everything works perfectly if I change the FetchType to EAGER but it seems that I shouldn't have to do that.
当然,如果我将 FetchType 更改为 EAGER,则一切正常,但似乎我不必这样做。
I am also aware of OpenEntityManagerInViewFilter but it seems that I shouldn't have to use that either if I keep all access to my entities in a DAO annotated with @Transactional (or through some other means that I'm not aware of).
我也知道 OpenEntityManagerInViewFilter 但似乎我不应该使用它,如果我在用 @Transactional 注释的 DAO 中保留对我的实体的所有访问权限(或通过其他一些我不知道的方式)。
I think that I might be approaching this problem incorrectly but I don't know what the correct approach is. How is one supposed to effectively use lazily loaded properties?
我认为我可能错误地解决了这个问题,但我不知道正确的方法是什么。应该如何有效地使用延迟加载的属性?
回答by Eddie
Thanks to Shailendra I started to look closely at the transaction and noticed that the transaction was never starting. With that information I did some investigation and found this: Spring @Transaction not starting transactions. I put <tx:annotation-driven/>in my servlet-context.xml file and suddenly the transaction for logDOI started up and everything worked correctly; I no longer got the LazyInitializationException. I am not at all clear as to why that worked. Any information on that would be appreciated.
感谢 Shailendra,我开始仔细查看交易并注意到交易从未开始。有了这些信息,我做了一些调查,发现:Spring @Transaction not started transactions。我放入<tx:annotation-driven/>了我的 servlet-context.xml 文件,突然 logDOI 的事务启动了,一切正常;我不再得到 LazyInitializationException。我完全不清楚为什么这样做。任何有关这方面的信息将不胜感激。
Update:
更新:
I figured it out. A critical piece of my problem was in the servlet-context.xml file. This is what it looked like
我想到了。我的问题的一个关键部分是在 servlet-context.xml 文件中。这是它的样子
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
<!-- Enables the Spring MVC @Controller programming model -->
<annotation-driven />
<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />
<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<context:component-scan base-package="org.myapp.doi" />
</beans:beans>
The major problem was in that context:component-scan line. Spring MVC creates two context which beans are instantiated in: the root application context defined with the contextConfigLocation parameter in the web.xml file and the servlet context defined in the DispatcherServlet in the web.xml file. The servlet context could see the application context but not the other way around. Now, as a result of context:component-scan being defined in the servlet context and scanning the entireapplication namespace, my DAO was being instantiated in the servlet context. However, the transaction annotation scanning was being done in the application context and the AOP proxy stuff for it could not be done from there. Simply modifying the context:component-scan in the servlet context to scan only the MVC controllers (<context:component-scan base-package="org.myapp.doi.web" />fixed everything; the DAO was being created in the application context and properly setup for transactions.
主要问题是在这种情况下:组件扫描线。Spring MVC 创建了两个实例化 bean 的上下文:使用 web.xml 文件中的 contextConfigLocation 参数定义的根应用程序上下文和 web.xml 文件中的 DispatcherServlet 中定义的 servlet 上下文。servlet 上下文可以看到应用程序上下文,但反过来不行。现在,由于在 servlet 上下文中定义了 context:component-scan 并扫描了整个应用程序命名空间,因此我的 DAO 在 servlet 上下文中被实例化。但是,事务注释扫描是在应用程序上下文中完成的,并且无法从那里完成 AOP 代理的工作。只需修改 servlet 上下文中的 context:component-scan 以仅扫描 MVC 控制器(<context:component-scan base-package="org.myapp.doi.web" />固定一切;DAO 是在应用程序上下文中创建的,并为事务正确设置。
回答by Shailendra
The best way to solve this would be to first understand why and when the session is closed inspite of being under single transaction. And the best way for that would be to enable the hibernate (as you are using hibernate as JPA provider) logging level to DEBUG in log4j configuration and track where the sessions are closed. This would give you a clear picture. Although the stack trace suggests that the underlying session was closed but there are obviously no reason why ? You can post the relevant debug/info messages logged.
解决这个问题的最好方法是首先了解会话关闭的原因和时间,尽管在单个事务下。最好的方法是在 log4j 配置中启用休眠(因为您使用休眠作为 JPA 提供程序)日志记录级别到调试并跟踪会话关闭的位置。这会给你一个清晰的画面。虽然堆栈跟踪表明底层会话已关闭,但显然没有原因?您可以发布记录的相关调试/信息消息。
Also you can set up logging for spring framework to track the transaction management infrastructure
您还可以为 spring 框架设置日志记录以跟踪事务管理基础设施
The logs give fairly good messages on when the underlying session was closed and transaction committed.
日志在底层会话关闭和事务提交时给出了相当好的消息。
For e.g.,
例如,
opened session at timestamp: 13476520251
............
...........
after transaction begin
..............
select x,y,z from......
...............
...commit
..........
..flushing session
..after transaction completion
..closing session
回答by Junbang Huang
I also got this problem, but inspired by your answer, I solve it. Here is it.
我也遇到了这个问题,但受到您回答的启发,我解决了它。就这个。
My application try to use Jpa Repository to handle the data in a rest controller, which turns out got the no session error. Here is the code:
我的应用程序尝试使用 Jpa 存储库来处理休息控制器中的数据,结果发现没有会话错误。这是代码:
@RequestMapping(value = "/create", method = POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Void> create(@RequestBody Map<String, Object> params) {
Project project = Factory.project().build(params);
project = repository.save(project);
return ResponseEntity.ok().build();
}
According to this postand this post, we know that beans in servlet context can reference beans in the application context. So the TransactionManager can not access to this rest controller bean, resulting this error.
根据这篇文章和这篇文章,我们知道 servlet 上下文中的 bean 可以引用应用程序上下文中的 bean。所以 TransactionManager 不能访问这个 rest 控制器 bean,导致这个错误。
Solution, creating a middle layer bean application context between rest controller and repository, encapsulating those code. I try it, it works fine. Update the code later.
解决方案,在 rest 控制器和存储库之间创建一个中间层 bean 应用程序上下文,封装这些代码。我试了一下,效果很好。稍后更新代码。
回答by ThierryB
First, for any dependency set with @Autowired, you must declare a Spring Bean foreach DAO you will use, and it will be injected at runtime.
首先,对于使用@Autowired 设置的任何依赖项,您必须为您将使用的每个 DAO 声明一个 Spring Bean,并且它将在运行时注入。
Maybe these DAO need to have a sessionFactory reference like this:
也许这些 DAO 需要有这样的 sessionFactory 引用:
<!-- a dao in which you inject your Hibernate SessionFactory bean by its own id -->
<bean id="userDAO" class="com.enterprise.model.dao.UserDAO">
<property name="sessionFactory">
<ref bean="sessionFactoryId" />
</property>
</bean>
<!-- activate the @Repository annotation -->
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
Secondly, maybe you forget to add this inside your Spring XML configuration:
其次,也许您忘记在 Spring XML 配置中添加它:
<!-- Add JPA support -->
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
</property>
</bean>
<!-- Add Transaction support -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="emf"/>
</bean>
Third, your interceptor stuff:
第三,你的拦截器的东西:
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="interceptors">
<list>
<ref bean="openEntityManagerInViewInterceptor"/>
</list>
</property>
</bean>
<bean id="openEntityManagerInViewInterceptor"
class="org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor">
<property name="entityManagerFactory" ref="entityManagerFactory" />

