hibernate延迟加载的使用和部分源码解析

来源:互联网 发布:ati mac驱动 编辑:程序博客网 时间:2024/06/05 11:19

前段时间使用了hibernate的级联映射和延迟加载,发现,当要将使用了延迟加载的model转为json对象时总是会报错,网上查了些资料大多数都是说因为之前的session已经被关闭,这时要使用这个属性,需要使用这个session去查询数据库所以会报错,大致分为两种解决办法:1、json转换的时候,过滤掉延迟加载的属性;2:、使用openSessionInViewFilter,延长session对象的生命周期,使关闭session的时间为响应结束的时间;自己尝试了下发现,过滤掉属性的话,则失去了级联映射的意义,而使用openSessionInViewFilter的话,如果是返回给页面的是object的话,是可以使用,但转为json时还是会报错;

继续查找资料,发现hibernate延迟加载使用的是cglib动态代理,大致的实现是:当要使用延迟加载的对象的非主键属性时,先根据主键查询数据库得到属性值,参考文章:http://superleo.iteye.com/blog/243322/;所以当返回obejct时jsp可以得到相应的值,但转为json时,这种机制不能被触发,暂时没搞懂是因为什么原因导致转换出错,后面参考网上的资料尝试的解决了,原理不清楚,希望有前辈路过可以指教下,具体实现,使用的是MappingJackson2HttpMessageConverter,重写ObjectMapper

public class HibernateObjectMapper extends ObjectMapper{
public HibernateObjectMapper() {
disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
}
}

如果是MappingJacksonHttpMessageConverter的话,则将SerializationFeature改为MappingJacksonHttpMessageConverter中对应的属性,具体忘了。

至此,延迟加载基本可以使用,后面试图改写openSessionInViewFilter,改变其过多延长session周期这方面的缺陷,发现,没有openSessionInViewFilter,延迟加载也是可以使用的,这点,网上没找到资料,猜测是hibernate4做的改变,使得当session已关闭时,会重新openSession来执行查询,但这样又导致每个使用了延迟加载的属性,第一次使用都需要为其创建一个session,所以将事务添加至Controller层,让session在请求结束后才关闭,结果发现,bibernate并不会像想象中的那样,看到session没被关闭就使用当前线程中现有的这个session,而是继续openSession,而此时,再添加openSessionInViewFilter的话,则可以发现,达到我们要的目的:一个请求只openSession一次;

观察源码,

openSessionInViewFilter做了下面的工作

1、openSession

2、将session封装成SessionHolder,使用TransactionSynchronizationManager将SessionHolder绑定到当期线程中

3、不知道干啥子的

AsyncRequestInterceptor interceptor = new AsyncRequestInterceptor(sessionFactory, sessionHolder);
                asyncManager.registerCallableInterceptor(key, interceptor);
                asyncManager.registerDeferredResultInterceptor(key, interceptor);

这样子当我们当我们调用SpringSessionContext.getCurrentSession时,(基本都看不懂)源码为:

//获取当前线程绑定的指定对象

Object value = TransactionSynchronizationManager.getResource(this.sessionFactory);

       //如果是session直接返回

        if (value instanceof Session) {
            return (Session) value;
        }

      //如果是sessionHolder则返回sessionHolder.getSession

        else if (value instanceof SessionHolder) {
            SessionHolder sessionHolder = (SessionHolder) value;
            Session session = sessionHolder.getSession();
            if (!sessionHolder.isSynchronizedWithTransaction() &&
                    TransactionSynchronizationManager.isSynchronizationActive()) {
                TransactionSynchronizationManager.registerSynchronization(
                        new SpringSessionSynchronization(sessionHolder, this.sessionFactory, false));
                sessionHolder.setSynchronizedWithTransaction(true);
                // Switch to FlushMode.AUTO, as we have to assume a thread-bound Session
                // with FlushMode.MANUAL, which needs to allow flushing within the transaction.
                FlushMode flushMode = session.getFlushMode();
                if (flushMode.equals(FlushMode.MANUAL) &&
                        !TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
                    session.setFlushMode(FlushMode.AUTO);
                    sessionHolder.setPreviousFlushMode(flushMode);
                }
            }
            return session;
        }

        if (this.transactionManager != null) {
            try {
                if (this.transactionManager.getStatus() == Status.STATUS_ACTIVE) {
                    Session session = this.jtaSessionContext.currentSession();
                    if (TransactionSynchronizationManager.isSynchronizationActive()) {
                        TransactionSynchronizationManager.registerSynchronization(new SpringFlushSynchronization(session));
                    }
                    return session;
                }
            }
            catch (SystemException ex) {
                throw new HibernateException("JTA TransactionManager found but status check failed", ex);
            }
        }

        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            Session session = this.sessionFactory.openSession();
            if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
                session.setFlushMode(FlushMode.MANUAL);
            }
            SessionHolder sessionHolder = new SessionHolder(session);
            TransactionSynchronizationManager.registerSynchronization(
                    new SpringSessionSynchronization(sessionHolder, this.sessionFactory, true));
            TransactionSynchronizationManager.bindResource(this.sessionFactory, sessionHolder);
            sessionHolder.setSynchronizedWithTransaction(true);
            return session;
        }
        else {
            throw new HibernateException("Could not obtain transaction-synchronized Session for current thread");
        }

通过debug跟踪,发现,后两者并未使用到,大致原因为:我们已经使用spring管理session,当请求到达前,如果当前线程中没有绑定session,

则spring会替我们先openSession,并绑定到线程中,这与openSessionInViewFilter所实现的一样,但不知是什么原因,导致 但或者延迟加载对象时,使用了OpenSessioInViewFilter的话,不会再去openSession,未使用的话,则每个延迟加载属性的初始化都将直接openSession,或许与OpenSessionInViewFilter后面那三行代码有关

OpenSessioninVIewFilter后续将会在请求结束后解绑线程(Tomcat使用的是线程池,只是归还线程,并未关闭,所以需要将session解绑),并关闭session