【问题标题】:LazyInitializationException in JPA and HibernateJPA 和 Hibernate 中的 LazyInitializationException
【发布时间】:2012-09-14 16:00:05
【问题描述】:

我知道这个问题已经在这里和互联网上被问过很多次,我已经阅读了许多答案,但我仍然不明白解决这个问题的正确方法。我正在尝试使用 Spring MVC 和 JPA,每次访问延迟加载的属性时,我都会收到 LazyInitializationException。

这是我正在试验的一些代码:

@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());
        }
    }
}

我正在访问的实体:

@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

}

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

}

Spring MVC 控制器:

@Controller
@RequestMapping("/summary")
public class DOISummaryController {
    @Autowired
    MyDAO myDAO;

    @RequestMapping()
    public String DOISummary() {
        myDAO.logDOI();

        return "home";
    }
}

我的 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>

还有一个堆栈跟踪,根据要求:

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)

您可以看到,我正在尝试使用纯 JPA,并且只使用 Hibernate 作为 JPA 提供程序。

我了解该异常是由会话与实体分离引起的。但是,我认为如果我们当前处于事务中就不会发生这种情况,因为 logDOI 方法是用 @Transactional 注释的。

当然,如果我将 FetchType 更改为 EAGER,一切都会完美运行,但似乎我不应该这样做。

我也知道 OpenEntityManagerInViewFilter,但如果我在使用 @Transactional 注释的 DAO 中保留对我的实体的所有访问权限(或通过我不知道的其他方式),我似乎也不必使用它)。

我认为我可能错误地处理了这个问题,但我不知道正确的方法是什么。应该如何有效地使用延迟加载的属性?

【问题讨论】:

  • 好像和获取当前的Hibernate Session有关。您是否检查过在配置中正确注入了 Hibernate Session?你……在新版Spring Framework中有注解@Repository,我用它来做一些DAO。
  • 我认为你已经使用的@Entity是一样的。然后,您应该检查您在 JPA 属性中设置的深度是否足以加载任何 @Autowired 依赖项。
  • 您是否将当前的 Hibernate Session 注入到您的 JPA 管理器中?最后,我更喜欢使用OpenEntityManagerInViewInterceptor
  • @ThierryB 对不起,我对 Spring 和 JPA 还很陌生,我的知识不足以回答您的问题。你可以再详细一点吗?另外,由于我正在学习 JPA,所以在使用 OpenEntityManagerInViewInterceptor 之类的东西之前,我更愿意学习正确的使用方法。

标签: spring hibernate jpa


【解决方案1】:

感谢 Shailendra,我开始仔细查看交易,发现交易从未开始。有了这些信息,我做了一些调查,发现:Spring @Transaction not starting transactions。我将&lt;tx:annotation-driven/&gt; 放入我的servlet-context.xml 文件中,突然logDOI 的事务启动并且一切正常;我不再得到 LazyInitializationException。我完全不清楚为什么会这样。任何有关这方面的信息将不胜感激。

更新:

我想通了。我的一个关键问题是在 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>

主要问题是在那个上下文中:组件扫描行。 Spring MVC 创建 bean 实例化的两个上下文:在 web.xml 文件中使用 contextConfigLocation 参数定义的根应用程序上下文和在 web.xml 文件中的 DispatcherServlet 中定义的 servlet 上下文。 servlet 上下文可以看到应用程序上下文,但反之则不行。现在,由于在 servlet 上下文中定义了 context:component-scan 并扫描了整个应用程序命名空间,我的 DAO 正在 servlet 上下文中被实例化。但是,事务注释扫描是在应用程序上下文中完成的,并且无法从那里完成它的 AOP 代理内容。只需修改 servlet 上下文中的 context:component-scan 以仅扫描 MVC 控制器(&lt;context:component-scan base-package="org.myapp.doi.web" /&gt; 修复了所有问题;DAO 正在应用程序上下文中创建并正确设置事务。

【讨论】:

  • 很高兴它对您有所帮助。我试图进一步挖掘,看起来如果你为 tx:annotation-driven 放置 name 属性,那么应该在 @Transaction 注释中明确提到该名称(它有一个属性 transaction-manager)。如果没有名称,则 Spring 假定事务管理器是一个名称等于 tx:annotation 属性名称的 bean(在您的情况下为“transactionManager”。Spring 事务文档也建议这样做。
  • 谢谢!这也为我解决了。在将 Spring 2.5 更新到 3.2 之后,我创建了一个 XML 文件,所以我被 LazyInitializationException 困了一天。在创建一个单独的 DispatcherServlet xml(Servlet 上下文)文件后,问题就解决了。非常感谢!
  • 这里建议了 Spring mvc 应用程序中两个上下文的最佳实践:stackoverflow.com/a/11471568/4344443。您真的不需要将 移动到 servlet-context.xml,只需将其保留在 application-context.xml 中。在你的 application.xml 中定义这个:
【解决方案2】:

解决此问题的最佳方法是首先了解会话关闭的原因和时间,尽管处于单一事务之下。最好的方法是在 log4j 配置中启用休眠(当您使用休眠作为 JPA 提供程序时)日志级别到 DEBUG 并跟踪会话关闭的位置。这会给你一个清晰的画面。尽管堆栈跟踪表明底层会话已关闭,但显然没有原因?您可以发布相关的调试/信息消息记录。

您还可以为 spring 框架设置日志记录以跟踪事务管理基础架构

日志在底层会话何时关闭和事务提交时给出了相当好的消息。

例如,

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

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

【讨论】:

  • 嗯,这很有趣。我在我的 log4j 配置中为 org.hibernate 和 org.springframework.transaction 启用了调试日志记录。在那之后,我的日志中正好有一行谈论交易:DEBUG: 2012-09-17 09:51:55,976 org.springframework.transaction.annotation.AnnotationTransactionAttributeSource - Adding transactional method 'logDOI' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; '' 似乎交易甚至还没有开始。我不知道为什么。我的配置似乎与我看到的示例相同。
【解决方案3】:

我也遇到了这个问题,但是受到您的回答的启发,我解决了。就是这样。

我的应用程序尝试使用 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();
}

根据这个post 和这个post,我们知道servlet 上下文中的bean 可以引用应用程序上下文中的bean。所以 TransactionManager 不能访问这个 rest 控制器 bean,导致这个错误。

解决方案,在 rest 控制器和存储库之间创建一个中间层 bean 应用程序上下文,封装那些代码。我试了一下,效果很好。稍后更新代码。

【讨论】:

    【解决方案4】:

    首先,对于使用@Autowired 设置的任何依赖项,您必须为您将使用的每个DAO 声明一个Spring Bean,它将在运行时注入。

    也许这些 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" />
    

    其次,也许你忘记在你的 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>
    

    第三,你的拦截器:

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

    【讨论】:

    • 我这样做了,但仍然遇到同样的异常。
    • 我添加了一些有用的东西来加载@Repository 的东西。
    • 另外,DAO怎么不被声明为spring bean?它用@Repository 注释,@Autowired 注释正确提取它。 logDOI 中的logger.info("Fetched DOI: " + myDOI.getDoiKey()); 行正在以正确的输出正确执行。
    • 你为什么要我在我的 DAO 中注入 Hibernate 会话?我想专门使用 JPA,并且只使用 Hibernate 作为 JPA 实现。你让我做的不是和使用@PersistenceContext 注释一样吗?
    • 我就是这样做的,但我没有任何关于 jpa 的示例,请查看任何关于 JPA 和 Hibernate 作为持久性提供者的教程like these one
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-10-09
    • 1970-01-01
    • 2015-07-31
    • 1970-01-01
    • 1970-01-01
    • 2015-05-31
    • 2012-12-28
    相关资源
    最近更新 更多