数据库的延迟加载机制的应用、缓存管理
一、
1、 延迟加载
延迟加载(load)是Hibernate为提高程序执行效率而提供的一种机制,即只有真正使用该对象的数据时才会创建。
场合一:当用户要取数据库的一张表的一个字段,这个字段很可能就是一个字符,总而言之长度是比较短的。
场合二:当用户要取数据库的一张表的一个字段的值,而这个值很可能是blob类型,也许存取的是一个很大的视频文件。
两种场合的取数据的方法一样吗?是用load还是用get方法?
延迟加载的过程:通过代理(Proxy)机制来实现延迟加载。Hibernate从数据库获取某一个对象数据时、获取某一个对象的集合属性值时,或获取某一个对象所关联的另一个对象时,由于没有使用该对象的数据(除标识符外),Hibernate并不从数据库加载真正的数据,而只是为该对象创建一个代理对象来代表这个对象,这个对象上的所有属性都为默认值;只有在真正需要使用该对象的数据时才创建这个真正的对象,真正从数据库中加载它的数据。
对象的延迟加载
对象里的属性延迟加载
集合延迟加载
Account acc=(Account)session.load(Account.class,new Long(1)); //返回一个代理对象
System.out.println(acc.getId);
System.out.prontln(acc.getLonginName());
在只需要Account类的一个引用时,这种延迟加载就很有用。
如果只是访问对象标示符属性,就没有必要初始化代码。
Account acc=(Account)session.load(Account.class,new Long(1)); //返回一个代理对象
Order order=new Order();
order.setCreateTime(new Date());
order.setAccount(acc);
Session.save(order);
在这只需要Account实例来创建一个新的Order订单对象,当调用session.save(order)时,也只需要Account的主标示符值作为外键保存到订单表的对应字段中。这样就少执行一条select语句,从而提高查询效率。
Hibernate中默认采用延迟加载的情况主要有以下几种:
当调用Session上的load()方法加载一个实体时,会采用延迟加载。
当Session加载某个实体时,会对这个实体中的集合属性值采用延迟加载。(one-to-many)
当Session加载某个实体时,会对这个实体所单端关联(one-to-one, many-to-one)的另一个实体对象采用延迟加载。
能够懒加载的对象都是被改写过的代理对象,当相关联的session没有关闭时,访问这些懒加载对象(代理对象)的属性(getId和getClass除外)hibernate会初始化这些代理,或用Hibernate.initialize(proxy)来初始化代理对象;当相关联的session关闭后,再访问懒加载的对象将出现异常。
2、 关闭延迟加载
在加载单个实体时,如果不需要延迟加载,就可以使用session的get()方法。
当Session加载某个实体时,不需要对这个实体中的集合属性值延迟加载,而是要立即加载。这时可以在映射文件中针对 这个集合属性的配置元素(<set>,<bag>,<list>…)添加属性lazy=“false”。
当Session加载某个实体时,不需要对这个实体所单端关联的另一个实体对象延迟加载,就可以在映射文件中对这个单端关联的配置元素(<one-to-one>,<many-to-one> )添加属性lazy=“false”。
注意:one-to-one不能有constrained=true
二、抓取策略
在HQL语句中使用抓取连接查询,通过写一条left join fetch 语句把相关联的两个实体的数据一次性从数据库中加载上来。这样可以在特定情况下(同时需要使用到这两个实体的数据)减少SQL的数量来提高查询效率。
通过配置“抓取策略”来直接影响session的get()和load()方法的查询效果。
1.单端关联<many-to-one><one-to_one>上的抓取策略。
可以给单端关联的映射元素添加fetch属性。fetch属性有2个可选值
select:作为默认值,它的策略是当需要使用到关联对象的数据时,另外单独发送一条select语句抓取当前对象的关联对象的数据。即延迟加载。
join:它的策略是在同一条select语句使用连接来获得对象的数据和它关联对象的数据,此时关联对象的延迟加载失效。
2.集合属性上的抓取策略
在集合属性的映射元素上可以添加fetch属性,它有3个可选值。
select:作为默认值,它的策略是当需要使用所关联集合的数据时,另外单独发送一条select语句抓取当前对象的关联集合,即延迟加载。
join:在同一条select语句使用连接来获得对方的关联集合。此时关联集合上的lazy会失效。
subselect:另外发送一条查询语句(或子查询语句)抓取在前面查询到的所有实体对象的关联集合。这个策略对HQL的查询也起作用。
当fetch为join时,执行左外连接,这个时候,在加载Customer时,Customer所对应的Order值全部被加载到缓存中,如果Order中没有大数据,这个策略是一个不错的选择。
当fetch为subselect时,针对in有效,如果为select时,from Customer where id in(1,2,3),hibernate会把ID取出来,逐一的去取Order的值,效率比较低。这个时候subselect效率比较高,不管in里含有多少数据,在查询Order是,只会发出一条sql语句。把<set>集合中batch-size设置为一个比较合适的数值时也相当于fetch为subselect,你可以根据项目的因素来选择发出sql语句的次数。
HQL总是忽略映射文件中设置的预先抓取策略,即在HQL中使用预先抓取时,必须显示指明fetch关键字。然而不同的是,QBC则不会忽略映射文件中的预先抓取策略。
在实践开发项目过程中,不仅需要根据实际情况选择合适的抓取策略,而且需要通过不断的测试来验证这个策略是不是最有效率的。
三、缓存管理
1.缓存概述
缓存(cache)在java应用程序中是一组内存中的集合实例。它保存着永久性存储源(如硬盘上的文件或者数据库)中数据的备份,它的读写速度比读写硬盘的速度快。应用程序在运行时直接读写缓存中的数据,只在某些特定时刻安装缓存中的数据来同伴更新数据存储源。如果缓存中存放的数据量非常大,也会用硬盘作为缓存的物理介质。
缓存的作用就是降低应用程序直接读写永久性数据存储源的频率,从而增强应用的运行性能。
缓存的实现不仅需要作为物理介质的硬件(内存),同时还需要用于管理缓存并发访问和过期等策略的软件。
2 缓存范围分类
缓存的范围决定了缓存的生命周期及其可以被谁访问。缓存的范围分为以下三类:
1)事务范围:缓存只能被当前事务访问。缓存的生命周期依赖于事务的生命周期,当事务结束时,缓存也就结束生命周期。在此范围下,缓存的介质是内存。
2)进程范围:缓存被进程内的所有事务共享。这些事务有可能是并发访问缓存,因此必须对缓存采取必要的事务隔离机制。
3)集群范围:在集群环境中,缓存被一个机器或者多个机器的进程共享。缓存的数据被复制到集群环境中的每个进程节点,进程间通过远程通信来保证缓存中的数据一致性。对于大多数应用来说,应该慎用集群范围的缓存,因为访问的速度并不一定比直接访问数据库数据的速度快很多。
3. 缓存的并发访问策略
在进程范围或集群范围的缓存,会出现并发问题。因此可以设置4中类型的并发访问策略,每一种策略对应一种事务隔离级别。事务的隔离级别越高,并发性能就越低。
1)事务型(Transactional)策略
2)读写型(Read-Write)策略
3)非严格读写型(Nonstrict-read-write)策略
4)只读型(Read-only)策略
4.Hibernate中的缓存
Hibernate中提供两级缓存,第一级别的缓存是Session级别的缓存,它是属于事务范围的缓存。
第二级别的缓存是SessionFactory级别的缓存,它是属于进程范围范围的缓存,这一级别的缓存可以进行配置和更改,并且可以动态加载和卸载。
Hibernate还为查询结果提供一个查询缓存,它依赖于二级缓存。
5.一级缓存的管理
Session级别的缓存由hibernate自动管理。当应用程序调用Session的CRUD方法及调用查询接口的list(),iterate()等方法时,如果在Session缓存中还不存在相应的对象,Hibernate就会把改对象加入到Session缓存中。如果在Session缓存中已经存在这个对象,就不需要再去数据库加载而是直接使用缓存中的这个对象,可以减少访问数据库的频率,提高程序的运行效率。当Hibernate清理缓存时(默认是提交事务的时候),Hibernate会根据缓存中对象的状态来同步数据库中的数据状态,在关闭Session时,会清空Session缓存中的所有对象。
一级缓存不能控制缓存的数量,所以要注意大批量操作数据时可能造成内存溢出;可以用
evict(Object obj):从缓存中清除指定的持久化对象。
clear():清空缓存中所有持久化对象。
flush(): 进行清理缓存(此时缓存中的数据并不丢失)的操作,让缓存和数据库同步 执行一些列sql语句,但不提交事务。
commit():先调用flush() 方法,然后提交事务. 则意味着提交事务意味着对数据库操作永久保存下来。
可以写一个for循环,Session可以批量插入上万条数据。如下面的代码:
For(int i=0;i<10000;i++){
Session.save(object);
}
这样写的代码会产生一个什么问题?会使一万个对象的缓存全部存在于内存中,这样做加大了内存的压力。所以应该定期清理session的缓存。
当做批量插入或批量更新时,必须通过经常调用Session的flush()以及稍后调用clear()来控制一级缓存的大小,这样内存才能保证足够的空间。
for(int i=1;i<=50;i++){
Department dept = new Department();
dept.setName("软件"+i);
session.save(dept);
for(int j=1;j<=100;j++){
Employee emp = new Employee();
emp.setName("张三"+j);
emp.setSalary(j);
emp.setDept(dept
session.save(emp);
if(j%50==0){
session.flush();
session.clear();
}
}
}
6.二级缓存的管理
二级缓存是SessionFactory级别的缓存,它的使用过程如下:
1)执行条件查询的时候,发出“select * from table_name where …”这样的SQL语句查询数据库,一次获得所有的数据对象。
2)把获得的所有数据对象根据ID放入到二级缓存中。
3)当Hibernate根据ID访问数据对象时,首先从Session缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;还没查到,再查询数据库,把结果按照ID放入到二级缓存。
4)删除、更新和增加数据的时候,同时会更新到二级缓存中。
Hibernate的二级缓存策略是针对ID查询的缓存策略,对于调节查询则毫无作用。为此,hibernate提高了单独针对条件查询的查询缓存。
适合存放到二级缓存中的数据有:
1)很少被修改的数据。
2)不是很重要的数据,允许出现偶尔的并发的数据。
3)很多系统模块都要用到
4)不是私有的数据,是共享的
什么样的数据不适合放在二级缓存中???
财务数据 安全性的数据 也就是不想让别人看到的数据和特别重要的数据
1、二级缓存的管理
二级缓存是SessionFactory级别的缓存,它的使用过程如下:
1)执行条件查询的时候,发出“select * from table_name where …”这样的SQL语句查询数据库,一次获得所有的数据对象。
2)把获得的所有数据对象根据ID放入到二级缓存中。
3)当Hibernate根据ID访问数据对象时,首先从Session缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;还没查到,再查询数据库,把结果按照ID放入到二级缓存。
4)删除、更新和增加数据的时候,同时会更新到二级缓存中。
Hibernate的二级缓存策略是针对ID查询的缓存策略,对于调节查询则毫无作用。为此,hibernate提高了单独针对条件查询的查询缓存。
适合存放到二级缓存中的数据有:
1)很少被修改的数据。
2)不是很重要的数据,允许出现偶尔的并发的数据。
3)很多系统模块都要用到
4)不是私有的数据,是共享的
什么样的数据不适合放在二级缓存中???
财务数据 安全性的数据 也就是不想让别人看到的数据和特别重要的数据
2、查询缓存
如果在实际使用中对于某个条件查询语句经常使用相同的条件值进行查询,就可以启用查询缓存。
Hibernate的查询缓存策略的过程如下。
1)在第一次执行条件查询时,Hibernate首先根据这些条件值组成一个Query key,Query key包括条件查询的请求信息。
2)在后续的过程中用相同的条件值执行这个查询是,Hibernate就先根据这个Query key到查询缓存中查找对应的结果列表,如果存在,返回。如果不存在,查数据库,再把结果列表根据Query 可以存放在查询缓存中。
所以,只有当经常使用相同的参数值进行相同的条件查询时,才能从查询缓存策略中得到好处。
使用查询缓存的步骤
1.配置查询缓存
Hibernate提供了3种与查询相关的缓存区域。
默认的查询缓存区域:org.hibernate.cache.StandardQueryCache
用户自定义的查询缓存区域
时间戳缓存区域:org.hibernate.cache.UpdateTimestampCache
在ehcache的配置文件ehcache.xml中设置查询缓存区域的属性。
<ehcache>
<!-- 设置默认的查询缓存区域的属性 -->
<cache name="org.hibernate.cache.StandardQueryCache"
maxElementsInMemory="50"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="3600"
timeToLiveSeconds="7200"
/>
<!-- 设置时间戳缓存区域的属性 -->
<cache name="org.hibernate.cache.UpdateTimestampsCache"
maxElementsInMemory="500"
eternal="true"
overflowToDisk="true"
/>
<!-- 设置自定义命名查询缓存区域的属性 -->
<cache name="myCacheRegion"
maxElementsInMemory="1000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="3600"
timeToLiveSeconds="7200"
/>
</ehcache>
3、
打开查询缓存
在hibernate的全局配置文件中添加如下配置来启动查询缓存。
<!--启用查询缓存 -->
<property name="cache.use_query_cache">true</property>
在应用程序代码中使用查询缓存
虽然按以上步骤设置好查询缓存,但hibernate在执行条件查询时默认是忽略查询缓存的。如果希望启用查询缓存,应该调用Query接口的setCacheeable(true)方法。