让Spring 3中jsp的数据对象使用懒加载(FetchType.LAZY)与Controller的JSR 303并存

来源:互联网 发布:linux内存管理机制 编辑:程序博客网 时间:2024/06/05 02:28

本文出处:http://blog.csdn.net/chaijunkun/article/details/9083171

所谓懒加载这里我多说两句(知道的朋友请无视这一段),数据库中表A和表B都分别映射了JPA的Pojo对象。从逻辑上说表A中的某个字段要和表B的主键进行连接操作(inner join,left join)可以不用手工去显式写JPQL语句,而可以通过注解的方式去映射,当需要取对应的值时直接调用.get方法即可,例如:

分别映射时:

表A(员工表):

[java] view plaincopy
  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 plaincopy
  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 plaincopy
  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 plaincopy
  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 plaincopy
  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 plaincopy
  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 plaincopy
  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 plaincopy
  1. <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />  


修改了之后懒加载和JSR 303可以共存了。


经验分享,希望对大家有所帮助。


0 0
原创粉丝点击