你的位置:首页 > Java教程

[Java教程]Hibernate中延迟加载和缓存


什么是延迟加载?

延迟加载是指当应用程序想要从数据库获取对象时(在没有设置lazy属性值为false),Hibernate只是从数据库获取符合条件的对象的OId从而生成代理对象,并没有加载出对象

访问该对象的属性时才会加载出相应的值。简答来说就是尽可能的减少查询的数据量。

如何配置延迟加载

在Hibernate中通过.hbm配置文件中的lazy属性来陪值,并且lazy属性出现的位置不同其作用和取值也不同。下面来详细介绍其在不同位置的不同取值和作用

类Class标签中的lazy:

在类标签Class中出现的lazy取值有true(默认值 延迟加载)、false(立即加载)两种取值如下所示:

wpsCE55.tmp

如果lazy取值为false则表示应用程序从数据库获取对象时会立即加载所有属性值(不包含自定义类型属性)

以员工表为例测试用例如下:

员工表表结构:

wpsCE65.tmp

员工实体类:

wpsCE76.tmp

员工表的hbm映射文件:

wpsCE77.tmp

测试代码:

wpsCE78.tmp

测试结果:

wpsCE79.tmp

从上图的结果中我们可以看到emp对象并没有进行延时加载但是其保存部门(Dept)对象的引用的属性并没有进行加载。

我们再来看看当lazy属性的值为true时的结果

wpsCE7A.tmp

wpsCE7B.tmp

从上图我们可以看到lazy的属性不管是True还是false其结果都是一样的!这是为什么呢?Lazy属性的值为true是不是应该延时加载吗?

注意:我们再上面编写测试用例时获取员工对象是用的get方法wpsCE7C.tmp而我也在之前的博客中说过session对象的get方法不支持延时加载他会忽略掉类级别的lazy属性!我们把get方法换成load方法再来测试。

wpsCE7D.tmp

从上图中我们可以看到当lazy属性值为true时Hibernate并不会一次性加载出所有属性值,只有当程序需要时才去加载从而减少了和数据库交互的负担,提升了程序的性能,这也是延迟加载出现的目的!

多对一关联中的lazy               

如果想要在获取对象的同时立即加载与之关联的自定义类型属性就需要在其多对一配置中设置lazy属性,在此处lazy属性的取值为:proxy:延迟加载(默认值)、no-proxy:无代理延迟加载,false:立即加载

wpsCE7E.tmp

比如我们在实例一的基础上添加lazy属性值为false再来测试:

wpsCE7F.tmp

Set元素中的lazy属性

我们知道如果对象中存在其他实体的集合则需要在hbm文件中配置set元素来进行表间的映射,而在set元素中也可以添加lazy属性其取值为:true:延迟加载(默认值)、false:立即加载、extra:加强延时加载。

wpsCE80.tmp

在这里不再对false的取值进行测试主要来测试true和extra的区别。我们再实例一的基础上引入Dept(部门)类来进行测试:

部门实体类

wpsCE81.tmp

部门表映射文件:

wpsCE82.tmp

测试代码:

wpsCE83.tmp

lazy属性值为true:

wpsCE93.tmp

Lazy属性值为extra:

wpsCE94.tmp

wpsCE95.tmp

有延时加载而引发的no Session问题

当我们在编写基于分层的B/s程序时常常会因为Session提前关闭而数据没有加载完成而引发no Session的异常如图所示:

wpsCE96.tmp

该异常引发的原因时同城操作数据的代码编写在DAO层和Biz层但是这两层并不负责数据的展示而我们在jsp页面中对数据进行展示时Session早已关闭并且有与延迟加载的关系数据并没有加载到对象中,当jsp页面去访问对象属性时Hibernate尝试使用Session对象去和数据库交互时发现并没有可用的Session对象从而引发该异常。

对于此类问题同城的做法是利用过滤器(Filter)将Session对象存放在表示层。如下代码所示创建过滤器:

wpsCE97.tmp

然后在web.

wpsCE98.tmp

经过以上操作就可以解决no Session的问题。当然还有其他的解决方案,但这种方案是使用最多的也是较为完善的解决方案,

缓存      

缓存的定义

缓存是为了减少应用程序和数据库交互次数而将一些修改频率较低、查询频繁的非关键性数据单独开辟一块空间存放起来的一块空间!是以一定范围内的空间换取用户从数据库查询数据的速度和性能的一种解决方案!

通常缓存分为以下几类:

内部缓存、二级缓存、查询缓存以及第三方缓存实现。

内部缓存

在Hibernate中内部缓存又称为一级缓存和事务级缓存由Hibernate自动维护不可卸载。其生命周期和Session对象的生命周期相同,当Session关闭时该缓存也会被自动回收。如下所示:

wpsCE99.tmp

其运行结果如下:

wpsCE9A.tmp

从结果中我们可以发现两次查询数据库时Hibernate值是生成了一条sql语句也就是说只有第一次查询时和数据库进行了交互,并将查询出的对象放入了内部缓存当第二次查询时Hibernate发现内部缓存中已经存在该对象则直接将该对象返回不在和数据库进行交互,并且这两次查询的对象的内存地址是完全相同的,由此可以得出内部缓存中缓存的是对象的内存地址的引用而不是对象的各个属性值!

二级缓存

二级缓存是可配置的插件,是进程或集群范围内的缓存,可以被所有的Session共享

二级缓存的配置

在Hibernate中配置二级缓存的插件有很多下面使用EHCache插件为例来配置二级缓存。

1.引入如下jar包。

      ehcache-1.2.3.jar  核心库

      backport-util-concurrent.jar

      commons-logging.jar

   2.配置Hibernate.cfg.

<propertyname="hibernate.cache.use_second_level_cache">true</property>

3.配置二级缓存的供应商

<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>

注:property元素必须在mapping元素之上

4.配置可进入二级缓存的类

<class-cache usage="read-write" class="cn.happy.entity.Emp"/>

5.在Classpath目录下引入ehcache.

wpsCE9B.tmp

经过以上5个步骤就可以将Dept对象放入二级缓存了,下面编写测试用例

wpsCE9C.tmp

测试结果:

wpsCE9D.tmp

wpsCE9E.tmp

从结果中我们可以开出第二次查询部门时并没有生成生成sql语句但是我们两次打印出的对象却不是同一个内存地址,这是因为二级缓存中存放的并不是对象象的内存地址的引用而是对象的散装属性(可以看成是对象的各个属性值)所以我们访问二级换存时需要将这些散装属性重新再内存中拼装成一个完整的对象。如果你仔细看的话你会发现当我们第二次去访问员工集合时Hibernate还是会生成sql语句。那是因为以上二级缓存的配置是针对类针对类级别的。如果想要将员工集合也进行缓存的话就需要在hibernate.cfg.

wpsCE9F.tmp

注:该配置必须是mapping元素的下一个元素

这时我们再来运行测试用来结果如下:

wpsCEA0.tmp

wpsCEA1.tmp

看到结果后你可能会大吃一惊,我没配集合缓存时他只生成一条sql语句我陪完之后怎么变成两条sql了?但是你注意看着两条sql语句是一模一样的,都是根据员工对象的OID来进行查询的!那么员工对象的OID是从哪来的呢?没错,就是从二级缓存中获取的至于为什么只是缓存员工对象的OID而没有缓存其他属性值,是因为员工对象没有配置二级缓存其不能进入二级缓存也就没有办法从二级缓存中拿到它的其他属性值,我们对员工对象配置二级缓存后再来进行测试。

wpsCEB2.tmp

其结果如下:

wpsCEB3.tmp

查询缓存

查询缓存的配置

wpsCEB4.tmp

在hibernate.cfg.true);

下面编写测试用例进行测试:

wpsCEB5.tmp

测试结果:

wpsCEB6.tmp

注意:

1.查询缓存是基于二级缓存的在配置查询缓存时必须配置二级缓存否则将抛出如下异常wpsCEB7.tmp

2.在查询缓存中保存的是对象内存地址的引用而不是对象的散装属性。

3.查询缓存是根据两次HQL查询语句经Hibernate内部转化后生成的sql语句是否一样留在决定是否和数据库进行交互而不是根据其对象的OID如下面的测试用例虽然查询的是OID相同的对象但还是会和数据库进行交互!

wpsCEB8.tmp

测试结果:

wpsCEB9.tmp

延迟加载和缓存遗留问题

Lazy属性和fetch属性连用

在一对多或者多对多检索策略由lazy和fetch共同确定,Lazy:决定关联对象初始化时机,Fetch:决定SQL语句构建形式。Fetch属性的取值为:Join:迫切左外连接、Select:多条简单SQL(默认值)、Subselect:子查询。当Fetch属性取值为join是将忽略lazy属性采用立即加载策略。例如插叙编号为5的部门,即便将部门映射文件中映射员工集合的set元素中的lazy属性设置为true或extra其还是会立即加载出该部门下的所有员工的集合,其Hibernate内部生成的sql语句如下:

wpsCEBA.tmp

Query接口的list方法和iterate方法的区别

1.返回的类型不一样,list返回List,iterate返回Iterator,
2.获取数据的方式不一样,list会直接查数据库,iterate会先到数据库中把id都取出来,然后真正要遍历某个对象的时候先到缓存中找,如果找不到,以id为条件再发一条sql到数据库,这样如果缓存中没有数据,则查询数据库的次数为n+1。
3.iterate会查询2级缓存,list 只会缓存,但不会使用缓存(除非结合查询缓存)。
4.list中返回的List中每个对象都是原本的对象,iterate中返回的对象是代理对象

缓存的内部存储实现

1.在缓存中都是以map集合的形式对象数据

2.一级缓存和二级缓存中都是以对象的OID作为map结合的key值而查询缓存是以Hibernate内部生成的sql语句作为key值

3.一级缓存和查询缓存中map集合的value值存放的是内存对象的引用,而二级缓存中存放的是对象的散装属性。

4.当应用程序需要从数据库获取数据时Hibernate会以此检索一级缓存或查询缓存>二级缓存或查询缓存中是否有符合条件的数据如果有则直接返回不再进行和数据库的交互,反之则生成sql语句去和数据库进行交互获取相应的数据再进行返回并依次将给数据放入一级缓存>二级缓存或查询缓存

将二级缓存保存到硬盘

在classpath目录下的ehcache.