前言:题目有点大了(#>.<),主要就是讨论一下,在使用JPA或者Hibernate的时候(JPA虽然有Hibernate实现,但是还是拆开来说),多表关联加载时机方案的选择的问题。Lazy给很多开发带来困扰,也有很多文章讲解了这些问题,下面是我个人的解决思路和解决方案。
关键词: JPA Hibernate 动态代理 Aop
会话管理
无论是使用JPA或者Hibernate在延迟加载处理中,我们关心的问题无非就是,Session或者EntityManager什么时候打开,什么时候关闭!而解决延迟加载的问题核心在于解决Session或者EntityManager的开关时效管理。
视图层(JPA)
在WEB项目中,基于Filter接口规范来处理,配合Spring使用,在Spring ORM的支持包中已经有解决方案:
OpenEntityManagerInViewFilter
1. 在entity中的@OneToMany、@OneToOne等中设置fetch = FetchType.LAZY
2. 标准的Filter,在web.xml配置即可
<filter-mapping>
- <filter>
- <filter-name>jpaFilter</filter-name>
- <filter-class>
- org.springframework.orm.jpa.support. OpenEntityManagerInViewFilter
- </filter-class>
- <init-param>
- <param-name>entityManagerFactory</param-name>
- <param-value>entityManagerFactory</param-value>
- </init-param>
- </filter>
- <filter-mapping>
- <filter-name>jpaFilter</filter-name>
- <url-pattern>*.action</url-pattern>
- </filter-mapping>
</filter-mapping> 3. OpenEntityManagerInViewFilter处理的是Spring通过在ServletContext 中载入配置,通过WebApplicationContextUtils.getWebApplicationContext(ServletContext sc)方法获得ApplicationContext,如果想要自行载入构建的ApplicationContext被OpenEntityManagerInViewFilter处理,需要修改其源代码。
OpenEntityManagerInViewInterceptor
这个Interceptor使用在Spring MVC的结构,使用Spring MVC的可以在applicationContext-*.xml中定义注入即可,它的ref指向EntityManagerFactory ,它也是SimpleUrlHandlerMapping的interceptors属性的list之一,使用Spring MVC的开发人员可以看一下,配置很方便。
视图层(Hibernate)
Hiberate和JPA在Spring中的解决方案是非常类似的。
OpenSessionInViewFilter
1. 必须在Hibernate的配置文件*.hbm.xml中,对class定义lazy="true"或者在many-to-one、
one-to-one映射关心中定义lazy="proxy"。
2. 标准的filter配置,在web.xml中
- <filter>
- <filter-name>hibernateFilter</filter-name>
- <filter-class>
- rg.springframework.orm.hibernate3.support.OpenSessionInViewFilter
- </filter-class>
- <!-- 是否单个Session-->
- <init-param>
- <param-name>singleSession</param-name>
- <param-value>false</param-value>
- </init-param>
- <!-- 你在applicationCointext*.xml中配置的SessionFactory bean的id名-->
- <init-param>
- <param-name>sessionFactoryBeanName</param-name>
- <param-value>mySessionFactory</param-value>
- </init-param>
- </filter>
- <filter-mapping>
- <filter-name>hibernateFilter</filter-name>
- <url-pattern>*.action</url-pattern>
- </filter-mapping>
<filter></filter><filter-mapping><url-pattern></url-pattern> </filter-mapping> 3. OpenSessionInViewFilter处理的是Spring通过在ServletContext 中载入配置,通过WebApplicationContextUtils.getWebApplicationContext(ServletContext sc)方法获得ApplicationContext,如果想要自行载入构建的ApplicationContext被OpenSessionInViewFilter处理,需要修改其源代码。
OpenSessionInViewInterceptor
这个Interceptor使用在Spring MVC的结构,使用Spring MVC的可以在applicationContext-*.xml中定义注入即可,它的ref指向SessionrFactory ,它也是SimpleUrlHandlerMapping的interceptors属性的list之一
服务层
动态代理(JPA)
使用动态代理AOP结构,来管理EntityManager,这种方案不依赖于容器,在后台服务、测试中都可以良好使用。先看代码:
OpenEntityManagerHandler.java
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- import java.lang.reflect.Proxy;
- import javax.persistence.EntityManager;
- import javax.persistence.EntityManagerFactory;
- import org.apache.log4j.Logger;
- import org.springframework.context.ApplicationContext;
- import org.springframework.orm.jpa.EntityManagerHolder;
- import org.springframework.transaction.support.TransactionSynchronizationManager;
- import cn.neto.util.JSFUtils;
- public class OpenEntityManagerHandler implements InvocationHandler {
- private static final Logger logger = Logger
- .getLogger(OpenEntityManagerHandler.class);
- private ApplicationContext ctx = null;
- private EntityManagerFactory emFactory = null;
- private Object obj;
- public OpenEntityManagerHandler() {
- if (ctx == null)
- ctx = getApplicationContext();
- if (emFactory == null)
- emFactory = (EntityManagerFactory) ctx.getBean(
- "entityManagerFactory", EntityManagerFactory.class);
- }
- public Object invoke(Object arg0, Method arg1, Object[] arg2)
- throws Throwable {
- Object result = null;
- boolean isOpen = false;
- if (TransactionSynchronizationManager.hasResource(emFactory)) {
- isOpen = true;
- } else {
- // 开
- logger.info("[open entity manager!]");
- EntityManager em = emFactory.createEntityManager();
- TransactionSynchronizationManager.bindResource(emFactory,
- new EntityManagerHolder(em));
- }
- try {
- result = arg1.invoke(this.obj, arg2);
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- try {
- // 关
- if (!isOpen) {
- EntityManagerHolder emHolder = (EntityManagerHolder) TransactionSynchronizationManager
- .unbindResource(emFactory);
- emHolder.getEntityManager().close();
- logger.info("[close entity manager!]");
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- return result;
- }
- public Object bind(Object obj) {
- this.obj = obj;
- return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
- .getClass().getInterfaces(), this);
- }
- private ApplicationContext getApplicationContext(){
- //TODO
- /*
- *自行获得ApplicationContext
- */
- }
- }
演示
接口
ITask.java
- public interface ITask {
- public void process();
- }
实现
ITaskImpl.java
- public class ItaskImpl implements Itask{
- public void process(){
- System.out.println(“Hello World!”);
- }
- }
使用
- OpenEntityManagerHandler handler = new OpenEntityManagerHandler();
- ITask task = (ITask) handler.bind(new ITaskImpl());
- task.process();
运行结果
- [open entity manager!]
- Hello World!
- [close entity manager!]
是不是很简单!
深入
以上的方案不修改的Service/DAO模型,侵入性小,测试方便,但是有一些局限性。更良好的方案可以将OpenEntityManagerHandler进行一些修改,
1. 加入private String FACTORY_NAME=DEFAULT_ENTITYMANGER_FACTORY,修改获取EntityManagerFactory的方法。
2. 在applicationContext-*.xml中配置bean,并ref指向EntityManagerFactory的bean。
3. 修改你的Service/DAO模型,引入OpenEntityManagerHandler。
留给有兴趣的自己研究,修改一下。
补充
Hiberate的解决方案和JPA非常类似,不再重复了。
使用IBatis
不是开玩笑,标准并不能决定一切,JPA和Hiberante也不是持久层的唯一解决方案(JDO等其他不用再考虑了,慢慢的退出舞台吧)!
Ibatis严格来讲不是一个标准ORM框架(真的不太喜欢用框架这个词,头大^.^),如果你本人的SQL造诣比较精深,无论是新项目的开发或者老项目的改造Ibatis都能给你不错的惊喜!
Ibatis延迟加载的开关参数是lazyLoadingEnabled,具体内容参考官方文档,开发者本身SQL水平的高低直接影响Ibatis的使用优劣。
Ibatis现在是apache的顶级项目http://ibatis.apache.org/,文档(有中文的,好像是夏新或者曹晓刚翻译的,有点记得不太清楚了),范例应有尽有,自己去Happy吧。
范例运行
范例中是一个简单的动态代理运行的例子,其中把载入Spring和JPA、Hiberante的依赖去掉了,让大家可以看一下结构,真正配合Spring、JPA/Hiberante使用,需要大家自己增添代码,感兴趣的自己丰富一下代码,希望对你有所启发。
总结
文中提到了四种解决方案,
第一、 使用OpenEntityManagerInViewFilter/OpenSessionInViewFilter,在Spring MVC结构中还可以更方便简洁的使用 OpenEntityManagerInViewInterceptor/OpenSessionInViewInterceptor。
第二、 使用非注入的AOP的结构,应用动态代理,不依赖于任何容器。
第三、 配合Spring使用,需要修改Service/DAO模型,引入自实现的OpenEntityManagerInterceptor/OpenSessionInterceptor
第四、 使用Ibatis。(这个貌似不能算方案#>.<)