讓Spring 3中jsp的數據對象使用懶加載(FetchType.LAZY)與Controller的JSR 303並存

本文出處:http://blog.csdn.net/chaijunkun/article/details/9083171,轉載請註明。由於本人不定期會整理相關博文,會對相應內容作出完善。因此強烈建議在原始出處查看此文。

最近維護一個之前做的項目,項目採用的是spring 3和hibernate 4 JPA做的。由於當時做項目的時候經驗偏少,又圖省事,所以使用了Hibernate中的懶加載,所謂懶加載這裏我多說兩句(知道的朋友請無視這一段),數據庫中表A和表B都分別映射了JPA的Pojo對象。從邏輯上說表A中的某個字段要和表B的主鍵進行連接操作(inner join,left join)可以不用手工去顯式寫JPQL語句,而可以通過註解的方式去映射,當需要取對應的值時直接調用.get方法即可,例如:

分別映射時:

表A(員工表):

[java] view plain copy

  1. import java.io.Serializable;  

  2.   

  3. import javax.persistence.Column;  

  4. import javax.persistence.GeneratedValue;  

  5. import javax.persistence.Id;  

  6.   

  7. public class A implements Serializable{  

  8.       

  9.     private static final long serialVersionUID = 8985074792763641465L;  

  10.   

  11.     @Id  

  12.     @GeneratedValue  

  13.     @Column(name= "idx")  

  14.     private Integer idx;  

  15.       

  16.     @Column(name="name", nullable= false)  

  17.     private String name;  

  18.       

  19.     @Column(name="departId", nullable= false)  

  20.     private Integer departId;  

  21.   

  22.     //getters and setters  

  23.   

  24. }  


表B(部門表):

[java] view plain copy

  1. import java.io.Serializable;  

  2.   

  3. import javax.persistence.Column;  

  4. import javax.persistence.GeneratedValue;  

  5.   

  6. import javax.persistence.Id;  

  7.   

  8. public class B implements Serializable{  

  9.       

  10.     private static final long serialVersionUID = 5994901985887071206L;  

  11.   

  12.     @Id  

  13.     @GeneratedValue  

  14.     @Column(name="departId")  

  15.     private Integer departId;  

  16.       

  17.     @Column(name="departName", nullable= false)  

  18.     private String departName;  

  19.       

  20.     //getters and setters  

  21.   

  22. }  


傳統的查詢方法中表A要想知道某個員工的部門名稱,需要通過A.departId然後再查詢B.departId=A.departId才能得知B.departName。所以在JPA中提供了一個更爲方便的方法:

只需要對主表(表A)做下面是修改即可:

[java] view plain copy

  1. import java.io.Serializable;  

  2.   

  3. import javax.persistence.Column;  

  4. import javax.persistence.FetchType;  

  5. import javax.persistence.GeneratedValue;  

  6. import javax.persistence.Id;  

  7. import javax.persistence.ManyToOne;  

  8.   

  9. public class A implements Serializable{  

  10.       

  11.     private static final long serialVersionUID = -8453322980651820968L;  

  12.   

  13.     @Id  

  14.     @GeneratedValue  

  15.     @Column(name= "idx")  

  16.     private Integer idx;  

  17.       

  18.     @Column(name="name", nullable= false)  

  19.     private String name;  

  20.       

  21.     @ManyToOne(fetch = FetchType.LAZY)  

  22.     @Column(name="departId", nullable= false)  

  23.     private B b;  

  24.       

  25.     //getters and setters  

  26.   

  27. }  


JPA標準中這種對象關聯默認是全加載,即查詢到A後根據映射關係會立即查詢對應的B,並將其注入到結果A中。但是我們爲了提高性能,會將映射方式改爲懶 加載(fetch= FetchType.LAZY),當結果A第一次調用getB()的時候纔會去查詢相應的B並將其注入。這就是懶加載


問題來了,框架並不知道我們什麼時候會調用get方法去加載關聯對象,項目中,我在Controller中查詢出來的結果往往是A,剩下的現實邏輯交給了jsp,然而JSP中會使用${A.b.departName}這樣的操作,一旦有這樣的代碼就會有如下提示:

org.hibernate.LazyInitializationException: could not initialize proxy - no Session

懶加載時在JSP處理時找不到底層的數據庫連接會話,造成語句無法執行,後來通過查閱資料,得到了如下方法:

在JPA配置文件applicationContext-jpa.xml中加入如下的攔截器:

[html] view plain copy

  1. <!--實體管理器工廠Bean -->  

  2. <bean id="entityManagerFactory"  class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">  

  3.     <property name="persistenceUnitName" value="default" />  

  4. </bean>  

  5. <!-- 建立視圖內攔截器來解決JPA中訪問延遲加載屬性時產生的無會話異常 -->  

  6. <!-- LazyInitializationException: could not initialize proxy no session -->  

  7. <!-- 此攔截器會注入到servlet配置中的DefaultAnnotationHandlerMapping中 -->  

  8. <bean name="openEntityManagerInViewInterceptor" class="org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor">  

  9.     <property name="entityManagerFactory">  

  10.         <ref bean="entityManagerFactory" />  

  11.     </property>  

  12. </bean>  


然後將這個攔截器注入到基於註解的默認註解處理器配置中(spring-servlet.xml):

[html] view plain copy

  1. <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">  

  2.     <property name="interceptors">  

  3.         <list>  

  4.             <ref bean="openEntityManagerInViewInterceptor" />  

  5.         </list>  

  6.     </property>   

  7. </bean>  


本來到這裏懶加載已經配置成功了,在JSP中也可以使用。但後來加入了JSR 303,這個攔截器就失效了,又提示“org.hibernate.LazyInitializationException: could not initialize proxy - no Session”,經過比對,只是在spring-servlet.xml中增加了一行配置:

[html] view plain copy

  1. <mvc:annotation-driven />  


這也是很多人說的最簡單的開啓JSR 303的配置方法。雖然JSR 303開啓了,但是重要的懶加載卻失效了,通過屏蔽該條註釋,懶加載又能正常工作了,但JSR 303又失效了。這也印證了我的猜想。難道就沒有魚和熊掌兼得的方法嗎?百度既然沒找到答案,還是得靠谷歌啊。後來找到了一個遇到類似問題的求助帖:

http://stackoverflow.com/questions/3230633/how-to-register-handler-interceptors-with-spring-mvc-3-0

其中有人回答:

By default, Spring will register a BeanNameUrlHandlerMapping, and a DefaultAnnotationHandlerMapping, without any explicit config required.
默認地,spring會自動註冊一個BeamNameUrlHandlerMapping和一個DefaultAnnotationHandlerMapping,沒有要求任何進一步的配置


If you define your own HandlerMapping beans, then the default ones will not be registered, and you'll just get the explicitly declared ones.
如果你定義了自己的HandlerMapping,則默認的就不會被註冊,你就能得到你所聲明的HandlerMapping

So far, so good.

目前還不錯,但是問題來了

The problem comes when you add <mvc:annotation-driven/> to the mix. This also declares its own DefaultAnnotationHandlerMapping, which replaces the defaults. 

當加入了配置<mvc:annotation-driven />,spring仍然會聲明自己的DefaultAnnotationHandlerMapping,並且替換默認的。

原來如此,怪不得之前的配置不起作用了。現在唯一的解決辦法就是去掉這個配置,但是JSR 303怎麼手動配置開啓呢?

在這篇Spring官方文檔中找到了答案

http://static.springsource.org/spring/docs/3.0.0.RC1/reference/html/ch05s07.html

[html] view plain copy

  1. <!-- Invokes Spring MVC @Controller methods -->  

  2. <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">  

  3.     <property name="webBindingInitializer">  

  4.         <!-- Configures Spring MVC DataBinder instances -->  

  5.         <bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">  

  6.             <property name="validator" ref="validator" />  

  7.         </bean>  

  8.     </property>  

  9. </bean>  

  10.   

  11. <!-- Creates the JSR-303 Validator -->  

  12. <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />  

之前我的spring-servlet.xml配置文件中關於AnnotationMethodHandlerAdapter是按照默認初始化的:

[html] view plain copy

  1. <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />  


修改了之後懶加載和JSR 303可以共存了。


經驗分享,希望對大家有所幫助。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章