为了提高系统性能,hibernate也使用了缓存机制。
在hibernate框架中,主要包含两个方面的缓存,一级缓存和二级缓存。
hibernate缓存的作用主要表现在以下两个方面:1) 通过主键(ID)加载数据的时候。 2) 延迟加载中。
一级缓存:
hibernate的一级缓存是由session提供的,因此它只存在session的生命周期中。
也就是说session关闭的时候该session所管理的一级缓存也随之被清除。
hibernate的一级缓存是session所内置的,默认开启,不能被卸载,也不能进行任何配置。
在缓存中的对象,具有持久性,session对象负责管理.
一级缓存的优点是使用同一个session对象多次查询同一个数据对象,仅对数据库查询一次。
一级缓存采用的是Key-Value的MAP方式来实现的。在缓存实体对象时,对象的主关键字ID是MAP的Key,实体对象就是对象的值。
所以说一级缓存是以实体对象为单位进行存储的。访问的时候使用的是主键关键字ID。
一级缓存使用的是自动维护的功能。但可以通过session提供的手动方法对一级缓存的管理进行手动干预。
evict()方法用于将某个对象从session的一级缓存中清除。clear()方法用于将session缓存中的方法全部清除。
二级缓存:
二级缓存的实现原理与一级缓存是一样的。也是通过Key-Value的Map来实现对对象的缓存。
二级缓存是作用在SessionFactory范围内的。因此它它可被所有的Session对象所共享。需要注意的是放入缓存中的数据不能有第三方的应用对数据进行修改。
=============================================
补充1:
事务提交后,一级缓存中的数据会被更新到数据库,如果二级缓存设置为读写,那么这份数据会同时更新到二级缓存。
缓存是介于应用程序和物理数据源之间,其作用是为了降低应用程序对物理数据源访问的频次,从而提高了应用的运行性能。缓存内的数据是对物理数据源中的数据的复制,应用程序在运行时从缓存读写数据,在特定的时刻或事件会同步缓存和物理数据源的数据。
缓存的介质一般是内存,所以读写速度很快。但如果缓存中存放的数据量非常大时,也会用硬盘作为缓存介质。缓存的实现不仅仅要考虑存储的介质,还要考虑到管理缓存的并发访问和缓存数据的生命周期。
Hibernate的缓存包括:Session的缓存 和 SessionFactory的缓存,
SessionFactory的缓存又可以分为两类:内置缓存 和 外置缓存,SessionFactory的外置缓存也被称为Hibernate的第二级缓存。
Session的缓存是内置的,不能被卸载,也被称为Hibernate的第一级缓存。
SessionFactory的内置缓存和Session的缓存在实现方式上比较相似:
SessionFactory的内置缓存:是SessionFactory对象的一些集合属性包含的数据,中存放了映射元数据和预定义SQL语句,映射元数据是映射文件中数据的拷贝,而预定义SQL语句是在Hibernate初始化阶段根据映射元数据推导出来,SessionFactory的内置缓存是只读的,应用程序不能修改缓存中的映射元数据和预定义SQL语句,因此SessionFactory不需要进行内置缓存与映射文件的同步。
SessionFactory的外置缓存:是一个可配置的插件。在默认情况下,SessionFactory不会启用这个插件。外置缓存的数据是数据库数据的拷贝,外置缓存的介质可以是内存或者硬盘。SessionFactory的外置缓存也被称为Hibernate的第二级缓存。
Session的缓存:是指Session的一些集合属性包含的数据。
Hibernate的这两级缓存都位于持久化层,存放的都是数据库数据的拷贝,那么它们之间的区别是什么呢?为了理解二者的区别,需要深入理解持久化层的缓存的两个特性:缓存的范围和缓存的并发访问策略。
持久化层的缓存的范围
缓存的范围决定了缓存的生命周期以及可以被谁访问。缓存的范围分为三类。
1 事务范围:缓存只能被当前事务访问。缓存的生命周期依赖于事务的生命周期,当事务结束时,缓存也就结束生命周期。在此范围下,缓存的介质是内存。事务可以是数据库事务或者应用事务,每个事务都有独自的缓存,缓存内的数据通常采用相互关联的的对象形式。
2 进程范围:缓存被进程内的所有事务共享。这些事务有可能是并发访问缓存,因此必须对缓存采取必要的事务隔离机制。缓存的生命周期依赖于进程的生命周期,进程结束时,缓存也就结束了生命周期。进程范围的缓存可能会存放大量的数据,所以存放的介质可以是内存或硬盘。缓存内的数据既可以是相互关联的对象形式也可以是对象的松散数据形式。松散的对象数据形式有点类似于对象的序列化数据,但是对象分解为松散的算法比对象序列化的算法要求更快。
3 集群范围:在集群环境中,缓存被一个机器或者多个机器的进程共享。缓存中的数据被复制到集群环境中的每个进程节点,进程间通过远程通信来保证缓存中的数据的一致性,缓存中的数据通常采用对象的松散数据形式。
对大多数应用来说,应该慎重地考虑是否需要使用集群范围的缓存,因为访问的速度不一定会比直接访问数据库数据的速度快多少。
持久化层可以提供多种范围的缓存。如果在事务范围的缓存中没有查到相应的数据,还可以到进程范围或集群范围的缓存内查询,如果还是没有查到,那么只有到数据库中查询。事务范围的缓存是持久化层的第一级缓存,通常它是必需的;进程范围或集群范围的缓存是持久化层的第二级缓存,通常是可选的。
持久化层的缓存的并发访问策略
当多个并发的事务同时访问持久化层的缓存的相同数据时,会引起并发问题,必须采用必要的事务隔离措施。
在进程范围或集群范围的缓存,即第二级缓存,会出现并发问题。因此可以设定以下四种类型的并发访问策略,每一种策略对应一种事务隔离级别。
1 事务型:仅仅在受管理环境中适用。它提供了Repeatable Read事务隔离级别。对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读和不可重复读这类的并发问题。
2读写型:提供了Read Committed事务隔离级别。仅仅在非集群的环境中适用。对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读这类的并发问题。
3非严格读写型:不保证缓存与数据库中数据的一致性。如果存在两个事务同时访问缓存中相同数据的可能,必须为该数据配置一个很短的数据过期时间,从而尽量避免脏读。对于极少被修改,并且允许偶尔脏读的数据,可以采用这种并发访问策略。
4只读型:对于从来不会修改的数据,如参考数据,可以使用这种并发访问策略。
事务型并发访问策略是事务隔离级别最高,只读型的隔离级别最低。事务隔离级别越高,并发性能就越低。
什么样的数据适合存放到第二级缓存中?
1 很少被修改的数据
2 不是很重要的数据,允许出现偶尔并发的数据
3 不会被并发访问的数据
4 参考数据
不适合存放到第二级缓存的数据?
1 经常被修改的数据
2 财务数据,绝对不允许出现并发
3 与其他应用共享的数据。
Hibernate的二级缓存
如前所述,Hibernate提供了两级缓存,第一级是Session的缓存。由于Session对象的生命周期通常对应一个数据库事务或者一个应用事务,因此它的缓存是事务范围的缓存。第一级缓存是必需的,不允许而且事实上也无法比卸除。在第一级缓存中,持久化类的每个实例都具有唯一的OID。
第二级缓存是一个可插拔的的缓存插件,它是由SessionFactory负责管理。由于SessionFactory对象的生命周期和应用程序的整个过程对应,因此第二级缓存是进程范围或者集群范围的缓存。这个缓存中存放的对象的松散数据。第二级对象有可能出现并发问题,因此需要采用适当的并发访问策略,该策略为被缓存的数据提供了事务隔离级别。缓存适配器用于把具体的缓存实现软件与Hibernate集成。第二级缓存是可选的,可以在每个类或每个集合的粒度上配置第二级缓存。
Hibernate的二级缓存策略的一般过程如下:
1) 条件查询的时候,总是发出一条select * from table_name where …. (选择所有字段)这样的SQL语句查询数据库,一次获得所有的数据对象。
2) 把获得的所有数据对象根据ID放入到第二级缓存中。
3) 当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;查不到,再查询数据库,把结果按照ID放入到缓存。
4) 删除、更新、增加数据的时候,同时更新缓存。
Hibernate的二级缓存策略,是针对于ID查询的缓存策略,对于条件查询则毫无作用。为此,Hibernate提供了针对条件查询的Query缓存。
Hibernate的Query缓存策略的过程如下:
1) Hibernate首先根据这些信息组成一个Query Key,Query Key包括条件查询的请求一般信息:SQL, SQL需要的参数,记录范围(起始位置rowStart,最大记录个数maxRows),等。
2) Hibernate根据这个Query Key到Query缓存中查找对应的结果列表。如果存在,那么返回这个结果列表;如果不存在,查询数据库,获取结果列表,把整个结果列表根据Query Key放入到Query缓存中。
3) Query Key中的SQL涉及到一些表名,如果这些表的任何数据发生修改、删除、增加等操作,这些相关的Query Key都要从缓存中清空.
=============================================
补充2:
hibernate中有两种缓存级别,分别是Session级别(一级缓存)和SessionFactory级别(二级缓存)
一级缓存是session级共享:
save,update,saveOrUpdate,load,get,list,iterate,lock这些方法都会将对象放在一级缓存中,一级缓存不能控制缓存的数量,所以要注意大批量操作数据时可能造成内存溢出;可以用evict,clear方法清楚缓存中的内容。
二级缓存是SessionFactory级共享,
实现可插拔,通过修改配置文件参数就可以使用。hibernate内置了对EhCache,OSCache,TreeCache,SwarmCache的支持,可以通过实现CacheProvider和Cache接口加入。
Session的:save(此方法不适合native生成方式的主键),update,saveOrUpdate,list,iterator,get,load,以及Query,Criteria都会填充二级缓存,但查询缓存时,只有Session的iterator,get,load会从二级缓存中取数据。
Query,Criteria由于命中率较低,所以hibernate缺省是关闭的。Query查询命中低的一方面原因就是条件很难保证一致,且数据量大,无法保证数据的真实性。
我在这里以应用十分广泛的OSCache第三方缓存框架为例,谈谈具体实现。
*********实现OSCache二级缓存的配置方案***********
1.首先我们需要在hibernate.cfg.xml中配置几个属性:
< property name =”cache.use_second_level_cache” > true </ property >
<!– 选择第三方缓存框架支持 –>
< property name =”cache.provider_class” > org.hibernate.cache.OSCacheProvider </ property >
<!– 为了能够查看二级缓存的使用效果,还可以配置: –>
2.然后是哪些类需要配置二级缓存:(举例User实体类)
read-only:只读,效率最高。
read-write:读写,效率较低。但是能保证并发正确性。
nonstrict-read-write:非严格的读写,效率较高,不用加锁,不能保证并发正确性。例如帖子浏览量。
transactional:事务性缓存,可回滚缓存数据,一般缓存框架不带有此功能,实现很复杂。
这样hibernate.cfg.xml就配置完了。
3.配置映射文件User.hbm.xml,加入这句代码:
<cache usage=”read-write”/>
4.加入oscache.properties配置文件到classPath下,这个文件在hibernate源码包中有。里面有很多属性可以设置,如果只是简单实现,不需要修改。不修改的情况下,只有一个配置,是配置二级缓存的大小,这个视本机内存而定。默认1000。其中还有一个分布式的配置,可以设置主机IP等等。由于资源有限,我没有做实验。在此不做过多介绍。
OK,配置完成!
写一个测试类,测试吧。
补充一个测试方法:
通过输出st的信息,可以查看缓存的使用情况。
再补充一个清除二级缓存的方法:
HibernateUtil.getSessionFactory().evict(User. class ,id); // 清除单条缓存
=============================================
补充3:
二级缓存默认关闭,需要程序员手动开启。首先导入ehcache.jar二级缓存包。
然后,在src下添加ehcache.xml配置,同时,在hibernate.cfg.xml中启用二级缓存
<property name=”hibernate.cache.use_second_level_cache”> true</property>
<property name=”hibernate.cache.provider_class”> net.sf.ehcache.hibernate.EhCacheProvider</property>。
第三:指定使用二级缓存缓存哪种类型的对象,在hbm.xml中添加<cache region=”sampleCache1” usage=”read-only”/>
—还可参考
http://www.cnblogs.com/wean/archive/2012/05/16/2502724.html
http://blog.csdn.net/xc635960736/article/details/7049863
从图 2 的方框里的内容可以看出,这个 addresses 属性并不是我们熟悉的 HashSet、TreeSet 等实现类,而是一个 PersistentSet 实现类,这是 Hibernate 为 Set 接口提供的一个实现类。
PersistentSet 集合对象并未真正抓取底层数据表的数据,因此自然也无法真正去初始化集合里的 Address 对象。不过 PersistentSet 集合里持有一个 session 属性,这个 session 属性就是 Hibernate Session,当程序需要访问 PersistentSet 集合元素时,PersistentSet 就会利用这个 session 属性去抓取实际的 Address 对象对应的数据记录。
那么到底抓取那些 Address 实体对应的数据记录呢?这也难不倒 PersistentSet,因为 PersistentSet 集合里还有一个 owner 属性,该属性就说明了 Address 对象所属的 Person 实体,Hibernate 就会去查找 Address 对应数据表中外键值参照到该 Person 实体的数据。