从源码上分析hibernate的load和get之间的区别


对于hibernate的load和get之间的区别之前在网上找的一些资料,好多说法,所以不确定哪个才是对的,所以专门断点核实过,最后确定了两者之间的区别,本想自己写一篇文章了,后来发现以下这篇,写的也详细,给我自己写的话,可能会稍微简单点,所以就COPY过来了;

文章来源:http://www.iflym.com/index.php/code/201112050001.html


一说到hibernate的get和load之间的区别,大多数网上的都会说出如下的区别:get不走缓存,load走缓存;或者get不会使用二级缓存之类,然而这些都是错误的。其实两者没有大多的区别,真正的区别在于二者获取对象的方式,以及如何使用对象上。本文从源码分析上分析两者的具体区别。本方使用的hibernate 版本为3.6.3。

获取对象的API,二者都使用统一的方式调用,如下所示:

来源于sessionImpl

        loadapi:

                LoadEvent event =newLoadEvent(id, entityName,false,this);

        fireLoad( event, LoadEventListener.LOAD );

                 

               getapi:

        LoadEvent event =newLoadEvent(id, entityName,false,this);

        fireLoad(event, LoadEventListener.GET);

可以看出,二者的api都一样,主要的不一样在于,触发事件的方式不一样。load时使用LoadEventListener.LOAD而get时使用LoadEventListener.GET。我们看看两者的具体不一样:

publicstaticfinalLoadType GET =newLoadType("GET")

            .setAllowNulls(true)

            .setAllowProxyCreation(false)

            .setCheckDeleted(true)

            .setNakedEntityReturned(false);

     

    publicstaticfinalLoadType LOAD =newLoadType("LOAD")

            .setAllowNulls(false)

            .setAllowProxyCreation(true)

            .setCheckDeleted(true)

            .setNakedEntityReturned(false);

主要的区别在于:在allowNulls上get允许而load不允许,allowProxyCreation上get不允许而load允许。具体这两者的区别在哪儿,我们进一步地从源码上进行分析。

 

sessionImpl的fireLoad方法,最终会调用到 loadEventListener[i].onLoad(event, loadType)方法,而此方法会走向

DefaultLoadListener

publicvoidonLoad(LoadEvent event, LoadEventListener.LoadType loadType)throwsHibernateException

//152

event.setResult( proxyOrLoad(event, persister, keyToLoad, loadType) );

即调用proxyOrLoad中,这时get和load都是走的同一个逻辑,那么接下来看具体的proxyOrLoad实现

DefaultLoadEventListener.proxyOrLoad方法:

            // look for a proxy

            Object proxy = persistenceContext.getProxy(keyToLoad);

            if( proxy !=null) {//如果上下文中已有代理对象 分支1

                returnreturnNarrowedProxy( event, persister, keyToLoad, options, persistenceContext, proxy );

            }else{

                if( options.isAllowProxyCreation() )//允许创建代理,即load方式,分支2

                    returncreateProxyIfNecessary( event, persister, keyToLoad, options, persistenceContext );

                else//直接返回loaded对象,即get方式 分支3

                    returnload(event, persister, keyToLoad, options);

            }

这里有三个分支,第一个如果上下文中已经有代理对象,那么将继续根据get或load加载对象。而否则将分别进入到createProxyIfNecessary或load方法。

分支2:load创建代理
进入到createProxyIfNecessary方法,如下所示:;

Object existing = persistenceContext.getEntity( keyToLoad )

        if( existing !=null) {

            ......//已经在上下文中存在了

            returnexisting;

        }

        else{

            // return new uninitialized proxy 创建未实例化的代理,并直接返回之

            Object proxy = persister.createProxy( event.getEntityId(), event.getSession() );

            persistenceContext.getBatchFetchQueue().addBatchLoadableEntityKey(keyToLoad);

            persistenceContext.addProxy(keyToLoad, proxy);

            returnproxy;

        }

从上面的代码,可以看出,load的工作方式首先查询上下文(即缓存),如果存在则返回之。否则直接创建一个代理对象就返回,也就是说,这里根本就不会发送相应的sql进行数据库查询,而是直接返回相应的对象即可,不管给定的id是否在数据库中存在数据对象。

分支3:get获取对象
在load内部最终会调用到doLoad方法,我们看下doLoad的最终实现:

DefaultLoadEventListenerdoLoad方法doLoad(final LoadEvent event,final EntityPersister persister,final EntityKey keyToLoad,final LoadEventListener.LoadType options)

//首先从session缓存,即一级缓存中找

        Object entity = loadFromSessionCache( event, keyToLoad, options );

......

//再从二级缓存中找,如果开了二级缓存

        entity = loadFromSecondLevelCache(event, persister, options);

......

//最后,从数据库中找

        return loadFromDatasource(event, persister, keyToLoad, options);

通过对分支3即get内部实现的查看,我们会发现,get首先会直接一级缓存,再二级缓存,最后才到数据库中找。那么load的最终工作方式是否也是这样呢,load最终也会查数据吧,那么它的工作方式是如何呢?

load如何加载数据
从分支1我们会看出load不会直接访问数据库,只是简单地返回一个由底层封装的一个代理对象。由于笔者使用的版本为3.6.3,所以底层使用了javassist来进行代理构建,那么它底层使用了JavassistLazyInitializer来表示最后的对象的handler处理器。那么我们说load只有在访问属性的时候才会去最终访问数据,所以我们来看如何进行访问最终数据的。定位到该类的invoke方法(此类是实现类MethodHandler接口的invoke方法:

publicObject invoke(

            finalObject proxy,

            finalMethod thisMethod,

            finalMethod proceed,

            finalObject[] args)throwsThrowable {

        if(this.constructed ) {

            Object result;

//调用父类的invoke方法

                result =this.invoke( thisMethod, args, proxy );

            //处理特殊返回结果

            if( result == INVOKE_IMPLEMENTATION ) {

//最重要,这里最终会调用到获取最终数据的方法

                Object target = getImplementation();

                ......

//使用反射调用最终对象的最终方法,如xxx.getName()之类

                        returnValue = thisMethod.invoke( target, args );

    }

上面的实现,我们需要注意的有两点,一个是调用父类的invoke方法,第二个由是调用getImplementation。第一个invoke简单介绍下,其实就是由父类将通用的处理先行处理了,比如调用equals,hashcode以及内部使用的getHibernateLazyInitializer方法之类,具体实现可以查看类BasicLazyInitializer的具体实现。其它父类方法不能处理的由交由子类继续处理,即返回一个INVOKE_IMPLEMENTATION的标记,由子类再继续处理。第二个方法是这里的重点,它揭示了hibernate如何重新获取数据并实例化该数据,看代码如下所示:

publicfinalObject getImplementation() {

        initialize();//初始化对象,或者叫实例化对象

        returntarget;

    }

 

 

    publicfinalvoidinitialize()throwsHibernateException {

        if(!initialized) {

                target = session.immediateLoad(entityName, id);//调用session,立即加载相应对象

                initialized =true;

                checkTargetState();

                 }

    }

上面的代码,揭示了hibernate如何具体 初始化一个代理的对象,即调用session的immediateLoad方法加载一个对象。此immediateLoad我们说它的实现与get方法差不多,具体可以查看相应的实现,也是通过一级,二级缓存最后到数据库的一个过程。

然后还需要注意的就是这里的checkTargetState,为什么说load返回的对象,在调用属性时如果此对象为空,会报一个对象不存在的错误。其最终的实现就是这里的checkTargetState,看简单实现如下:

privatevoidcheckTargetState() {

        if( !unwrap ) {

            if( target ==null) {

                getSession().getFactory().getEntityNotFoundDelegate().handleEntityNotFound( entityName, id );

            }

        }

    }

其实就是如果为空,这里就抛出异常,并处理出,一般就是直接抛出一个异常。

分支1:已有对象处理
我们最后讨论在load和get时,如果上下文也有代理的情况,因为这只是其中处理的一个小的分支。也就是根据load和get的不同进行简单处理,具体实现如下:

LazyInitializer li = ( (HibernateProxy) proxy ).getHibernateLazyInitializer();

        if( li.isUnwrap() ) {

            returnli.getImplementation();//返回最终的实现

        }

        Object impl =null;

        if( !options.isAllowProxyCreation() ) {

            impl = load( event, persister, keyToLoad, options );//不允许代理时,直接返回之         

        }

//重新封装一次代理信息

        returnpersistenceContext.narrowProxy( proxy, persister, keyToLoad, impl );

所以,如果当前上下文上也有代理对象,则直接处理此代理对象,根据get或load的方式决定是否加载此对象还是解包再封装一下。

 总结:
从以上的源码分析来看,get和load的区别不是很大,主要是用处不一样。两个的区别在于获取数据的时机不一样,在正常的情况下两者都适合于常规的数据关系操作。如果很在意操作的对象信息一定要在数据库中存在的话,建议使用get,因为它的返回值直接就反映了数据库中是否存在这么一条数据;而load则是任何时候都会返回一个非null值,当要用的时候才对获取数据并验证,这与通常的理解有点偏差。但这种方式如果结合于数据库的约束器来使用的话,在某些情况下,可以减少数据库的查询数据的语句数。

至于缓存一说,get和load都会很好地利用到一级缓存和二级缓存,并且hibernate对于load产生的代理对象,专门在内部作了处理,使代理对象和常规的实体对象之间能够很好地协作和互转,所以不存在哪个有缓存,哪个无缓存的情况产生。

最后说下我个人的情况,由于个人对数据完整性要求较高,在保存数据到数据库之前,一定要验证数据的完整性,所以一直使用get方法,并由程序判断对象数据是否为null的情况;而load使用较少,加上使用了二级缓存(即以id缓存的主键对象),所以在数据查询上没有太大的数据访问问题。




 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值