我知道这个问题在这里和整个互联网上已经被问过无数次了,我已经阅读了许多答案,但是我仍然不了解解决这个问题的正确方法。我正在尝试使用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中(或者通过我不知道的其他方式),则似乎不必使用它。
我认为我可能会错误地解决此问题,但我不知道正确的方法是什么。应该如何有效地使用延迟加载的属性?
借助这些信息,我进行了一些调查,结果发现:Spring @Transaction无法启动事务。我放入<tx:annotation-driven/>servlet-context.xml文件,然后突然启动了logDOI的事务,并且一切正常。我不再遇到LazyInitializationException。我不清楚为什么这样做有效。任何有关的信息将不胜感激。
<tx:annotation-driven/>servlet-context.xml
更新:
我想到了。我的问题的关键在于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控制器(<context:component-scan base-package="org.myapp.doi.web" />固定一切;DAO是在应用程序上下文中创建的,并已正确设置事务。
<context:component-scan base-package="org.myapp.doi.web" />