No qualifying bean of type [javax.persistence.EntityManage] 異常問題的解決
引言: 在Spring Web專案中一般都會使用OpenEntityManagerInViewFilter來保證JPA session的正常關閉,在筆者的專案中,使用了Spring + Spring Data + JPA + Hibernate來的架構來組織專案,碰到了org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'entityManagerFactory' is defined 的異常資訊,將過一番查詢之後,方才發現問題是載入順序的問題....
1. 專案背景介紹
專案中使用的技術有: Spring+Spring Data, JPA, Hibernate等來貫穿專案的主題架構。
2. Session異常關閉的處理機制
在Java Web專案中使用Hibernate經常會遇到LazyInitializationException 。這是因為controller和model層(java程式碼)將通過JPA的一些啟用了延遲載入功能 的領域(如用getRefrence() 方法或者在關聯關係中採用fetch=FetchType.LAZY )返回給view層(jsp程式碼)的時候,由於載入領域物件的JPA Session已經關閉,導致這些延遲載入的資料訪問異常。
這時就可以使用OpenEntityManagerInViewFilter來將一個JPAsession與一次完整的請求過程對應的執行緒相繫結。
解決辦法:
3. 異常問題的出現<filter> <filter-name>Spring OpenEntityManagerInViewFilter</filter-name> <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class> <init-param> <!-- 指定org.springframework.orm.jpa.LocalEntityManagerFactoryBean在spring配置檔案中的名稱,預設值為entityManagerFactory 如果LocalEntityManagerFactoryBean在spring中的名稱不是entityManagerFactory,該引數一定要指定,否則會出現找不到entityManagerFactory的例外 --> <param-name>entityManagerFactoryBeanName</param-name> <param-value>entityManagerFactory</param-value> </init-param> </filter> <filter-mapping> <filter-name>Spring OpenEntityManagerInViewFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
在解決了Session異常關閉之後,在啟動伺服器的時候,就出現瞭如下異常資訊:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'entityManagerFactory' is defined
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:575)
at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1111)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:276)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:195)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1121)
at org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter.lookupEntityManagerFactory(OpenEntityManagerInViewFilter.java:222)
at org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter.lookupEntityManagerFactory(OpenEntityManagerInViewFilter.java:205)
at org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter.doFilterInternal(OpenEntityManagerInViewFilter.java:150)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:106)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:106)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:501)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:98)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:950)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1040)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:607)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:313)
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:662)
從異常資訊的分析可知道,OpenEntityManagerInViewFilter中對entityManagerFactory有依賴,需要對其在Spring中宣告的例項依賴。 但是沒有找到合適的例項。
可是entityManagerFactory的確已經宣告在了spring的配置檔案之中了。問題在哪裡呢?
4. 問題分析以及定位
專案中entityManagerFactory在Spring中的配置檔案定義的,經過檢查,工作正常,不存在問題。
<beans:bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<beans:property name="dataSource" ref="dataSource" />
<!--
<beans:property name="persistenceProviderClass" value="org.hibernate.ejb.HibernatePersistence"/>
-->
<beans:property name="jpaVendorAdapter">
<beans:bean
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<!-- hibernate properties definition -->
</beans:bean>
</beans:property>
<beans:property name="persistenceUnitName" value="myPersistenceUnit" />
<beans:property name="packagesToScan">
<beans:list>
<beans:value>com.creditease.bsettle.pay.model</beans:value>
</beans:list>
</beans:property>
<beans:property name="loadTimeWeaver">
<beans:bean
class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
</beans:property>
<beans:property name="jpaPropertyMap">
<beans:map>
<beans:entry key="showSql" value="true"></beans:entry>
<beans:entry key="generateDdl" value="false"></beans:entry>
<beans:entry key="hibernate.format_sql" value="true"></beans:entry>
<beans:entry key="hibernate.dialect"
value="org.hibernate.dialect.Oracle10gDialect"></beans:entry>
</beans:map>
</beans:property>
</beans:bean>
於是把問題的懷疑點聚焦在web.xml初始化檔案上,是否其中存在什麼問題呢?
在web.xml中存在2個地方進行spring例項的初始化操作:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:META-INF/spring-config.xml</param-value>
</context-param>
第二個位置是:
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:META-INF/applicationContext.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
存在錯誤異常的載入順序中,entityManagerFactory是在第二個位置中進行載入的。但是OpenEntityManagerInViewFilter是否載入的順序在其之前,然後就造成了這樣的問題呢?
經過試驗,果然是載入順序的問題,將資料庫載入的順序提前即可。
5. 總結
基於上面的問題,我們可以看到, OpenEntityManagerInViewFilter是在系統啟動過程中,優先被載入的,同時其對entityManagerFactory有依賴,就要求同時可以初始化entityManagerFactory的例項。
通過調整初始化的順序,即可很好的修正上述的問題。
參考資料:
1. http://whoosh.iteye.com/blog/1300721