简体   繁体   English

JPA和Hibernate中的LazyInitializationException

[英]LazyInitializationException in JPA and Hibernate

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: 我的Spring配置:

<?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? 如何有效地使用延迟加载的属性?

Thanks to Shailendra I started to look closely at the transaction and noticed that the transaction was never starting. 感谢Shailendra,我开始密切关注交易,并注意到交易从未开始。 With that information I did some investigation and found this: Spring @Transaction not starting transactions . 有了这些信息,我做了一些调查,发现了这个: Spring @Transaction没有启动交易 I put <tx:annotation-driven/> in my servlet-context.xml file and suddenly the transaction for logDOI started up and everything worked correctly; 我在我的servlet-context.xml文件中放了<tx:annotation-driven/> ,然后突然启动了logDOI的事务,一切正常; I no longer got the LazyInitializationException. 我不再得到LazyInitializationException。 I am not at all clear as to why that worked. 我一点也不清楚为什么这样有效。 Any information on that would be appreciated. 任何有关这方面的信息将不胜感激。

Update: 更新:

I figured it out. 我想到了。 A critical piece of my problem was in the servlet-context.xml file. 我的问题的一个关键部分是在servlet-context.xml文件中。 This is what it looked like 这就是它的样子

<?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. Spring MVC创建两个bean实例化的上下文:使用web.xml文件中的contextConfigLocation参数定义的根应用程序上下文,以及web.xml文件中DispatcherServlet中定义的servlet上下文。 The servlet context could see the application context but not the other way around. servlet上下文可以看到应用程序上下文,但不是相反。 Now, as a result of context:component-scan being defined in the servlet context and scanning the entire application namespace, my DAO was being instantiated in the servlet context. 现在,作为上下文的结果:组件扫描在servlet上下文中定义并扫描整个应用程序命名空间,我的DAO正在servlet上下文中实例化。 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. 但是,事务注释扫描正在应用程序上下文中完成,并且AOP代理程序无法从那里完成。 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. 只需修改上下文:servlet上下文中的组件扫描只扫描MVC控制器( <context:component-scan base-package="org.myapp.doi.web" />修复所有内容; DAO正在创建中应用程序上下文和正确设置事务。

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. 最好的方法是在log4j配置中启用hibernate(当你使用hibernate作为JPA提供程序时)将日志级别设置为DEBUG并跟踪会话关闭的位置。 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. 您可以发布记录的相关调试/信息消息。

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 eg, 例如,

opened session at timestamp: 13476520251
............
...........
after transaction begin
..............

select x,y,z from......
...............
...commit
..........
..flushing session
..after transaction completion
..closing session

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. 我的应用程序尝试使用Jpa Repository来处理休息控制器中的数据,结果发现没有会话错误。 Here is the code: 这是代码:

@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 post and this post , we know that beans in servlet context can reference beans in the application context. 根据这篇文章和这篇文章 ,我们知道servlet上下文中的bean可以在应用程序上下文中引用bean。 So the TransactionManager can not access to this rest controller bean, resulting this error. 因此TransactionManager无法访问此rest控制器bean,从而导致此错误。

Solution, creating a middle layer bean application context between rest controller and repository, encapsulating those code. 解决方案,在rest controller和repository之间创建一个中间层bean应用程序上下文,封装这些代码。 I try it, it works fine. 我试试,它工作正常。 Update the code later. 稍后更新代码。

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设置的任何依赖项,您必须声明将使用的Spring Bean foreach DAO,并且它将在运行时注入。

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" />

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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