Hibernate3.x教程(三) Hibernate缓存介绍

来源:互联网 发布:扑克变牌衣淘宝女装 编辑:程序博客网 时间:2024/05/02 07:00
缓存是介于应用程序和物理数据源之间,其作用是为了降低应用程序对物理数据源访问的频率,从而提高应用的运行性能。
 
Hibernate的缓存包括Sesssion缓存和SessionFactory全局缓存。
Session缓存被称为一级缓存,是Hibernate的内置缓存。使用内存存储,应用于事务范围之内,事务结束,缓存的生命周期同时结束。
SessionFactory全局缓存被称为二级缓存,它是SessionFactory的外置缓存,可以使用不同的缓存类库(如,ehcache、oscache等)实现。可以存储在内存或硬盘中,在整个应用程序中共享,应用结束时,缓存的生命周期才会结束。
 
下面分别介绍两种缓存的配置和使用。
 
Session缓存(一级缓存):
当调用Session的save(),update(),saveOrUpdate()更新对象,或使用load(),get(),list(),iterator(),scroll()等查询对象时,在Session缓存中不存在相应对象,都会把这些对象加入Session缓存。缓存大多都是以key-value形式进行保存的,Session缓存的key值主要由对象id和对象的类名来决定的,value就是实体对象。所以,之后在同一个事务范围内,Session再次根据id查询对象时,便会直接从Session缓存中将该对象返回,不再执行数据库查询操作。
例如,以下代码执行时,只会产生一条数据库查询语句:
Student student = studentDao.get(1);Student student2 = studentDao.get(1);
Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_, student0_.grade_id as grade4_0_0_ from school.student student0_ where student0_.id=?
 
前面已经提到Session缓存的生命周期之存在于当前事务,所以当Session关闭后,Session缓存就会无效。在studentDao.get()方法中执行session.close();会发现,上面代码仍然会执行两次SQL查询,表示Session缓存已经失效。
 
对于Session缓存,Hibernate提供了以下方法进行缓存管理:
  evit(Object obj)  将指定的持久化对象从一级缓存中清除,释放对象所占用的内存资源
  clear()  将一级缓存中的所有持久化对象清除,释放其占用的内存资源
  contains(Object obj) 判断指定的对象是否存在于一级缓存中.
  flush() 刷新一级缓存区的内容,使之与数据库数据保持同步.
 
Session缓存的问题就是命中率极低,因为Session的生命周期很短,尤其为了减少并发带来的数据问题,我们通常是倡导尽量使用短事务。
 
查询缓存:
注:Hibernate3.2版本之后查询缓存必须结合二级缓存才可以使用,示例代码是使用Hibernate3.1构建,本节内容可以忽略
查询缓存是缓存普通属性结果集的,对实体对象的结果集只缓存id,主要通过list()方法存储缓存并调用。
 
查询缓存的配置和使用(两者缺一不可):
1、在配置文件hibernate.cfg.xml中启用查询缓存:
<property name="hibernate.cache.use_query_cache">true</property>
2、在程序中必须手动启用查询缓存:
query.setCachable(true);
 
假设我们重复执行下面这段代码:
 session.createQuery("from Student").setCacheable(true).list(); session.createQuery("from Student").setCacheable(true).list();
它将会仅产生一条SQL查询:
Hibernate: select student0_.id as id0_, student0_.name as name0_, student0_.age as age0_, student0_.grade_id as grade4_0_ from school.student student0_
 
但是,这里要注意的是,查询缓存必须配合Session缓存使用,如果只启用查询缓存,不对查询对象启用二级缓存,则会大大降低查询效率。
因为,当第一次通过启用查询缓存的session进行语句查询时,系统只执行一次数据库查询将所有的记录取出,并将对象存入Session缓存,语句及id集合存入查询缓存;而当用户第二次查询该语句时,系统将先执行去查询缓存中查找,取出所有符合条件的id集合,如果这时候该对象的Session缓存没启用或在class缓存中已过期,系统将根据id,一个个去数据库load,实际上是进行了1+N次查询。
例如,我们修改上面的代码:
session.createQuery("from Student").setCacheable(true).list();session.clear();//session.close();session.createQuery("from Student").setCacheable(true).list();
查询一次后,清空Session缓存,或关闭Session,它将产生的SQL查询如下:
Hibernate: select student0_.id as id0_, student0_.name as name0_, student0_.age as age0_, student0_.grade_id as grade4_0_ from school.student student0_
Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_, student0_.grade_id as grade4_0_0_ from school.student student0_ where student0_.id=?
Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_, student0_.grade_id as grade4_0_0_ from school.student student0_ where student0_.id=?
Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_, student0_.grade_id as grade4_0_0_ from school.student student0_ where student0_.id=?
......
Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_, student0_.grade_id as grade4_0_0_ from school.student student0_ where student0_.id=?
Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_, student0_.grade_id as grade4_0_0_ from school.student student0_ where student0_.id=?
Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_, student0_.grade_id as grade4_0_0_ from school.student student0_ where student0_.id=?
Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_, student0_.grade_id as grade4_0_0_ from school.student student0_ where student0_.id=?
 
就像Session缓存一样,查询缓存同样存在命中问题,并且存在严重的性能问题(1+N)查询。
以上介绍,目的并非是告诉大家如何使用查询缓存,而是要慎用之。
在实际应用中,查询缓存通常会和二级缓存一起使用。
 
二级缓存:
二级缓存是由SessionFactory创建的所有Session对象共享使用, 二级缓存可使用第三方的缓存插件,如EHCache、OSChahe、SwarmCache、JBossCache。
 
二级缓存配置步骤:
1、在hibernate中启动二级类缓存,需要在hibernate.cfg.xml配置以下参数(以EHCache为例).
<hibernate-configuration>    <session-factory>        <!-- 设置二级缓存插件EHCache的Provider类-->        <property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>       <!-- 启动"查询缓存" -->       <property name="hibernate.cache.use_query_cache">true</property>    </session-factory></hibernate-configuration>
2、接下来,需要对EHCache进行配置,添加ehcache.xml配置文件,默认路径为class根目录:
<ehcache>    <diskStore path="java.io.tmpdir" />    <defaultCache            maxElementsInMemory="10000"            eternal="false"            timeToIdleSeconds="300"            timeToLiveSeconds="300"            overflowToDisk="false"            diskPersistent="false"            diskExpiryThreadIntervalSeconds="300"/>    <cache name="com.boya.hibernate.entity.Student"         maxElementsInMemory="10000"        eternal="true"         overflowToDisk="false"         timeToIdleSeconds="200"        timeToLiveSeconds="200"/>      </ehcache>
上面配置了默认缓存策略和学生对象的缓存策略:
<diskStore>:设置磁盘缓存的位置
<defaultCache>:默认缓存策略
<cache>:自定义缓存策略
name:cache的名称,必须是唯一的(ehcache会把这个cache放到HashMap里)。
maxElementsInMemory:cache 中最多可以存放的元素的数量。如果放入cache中的元素超过这个数值,有两种情况:1、若overflowToDisk的属性值为true,会将cache中多出的元素放入磁盘文件中。2、若overflowToDisk的属性值为false,会根据memoryStoreEvictionPolicy的策略替换cache中原有的元素。
maxElementsOnDisk:DiskStore中最大允许保存的对象数量,默认值为0,表示不限制。
eternal:设定缓存是否过期,如果是,则它的超时设置会被忽略。
overflowToDisk:如果内存中数据超过内存限制,是否要缓存到磁盘上。
timeToIdleSeconds:对象空闲时间,指对象在多长时间没有被访问就会失效。只对eternal为false的有效。默认值0,表示一直可以访问。
timeToLiveSeconds:对象存活时间,指对象从创建到失效所需要的时间。只对eternal为false的有效。默认值0,表示一直可以访问。
diskPersistent:是否在磁盘上持久化缓存。指重启jvm后,数据是否有效。默认为false。
diskExpiryThreadIntervalSeconds:对象检测线程运行时间间隔。标识对象状态的线程多长时间运行一次。
diskSpoolBufferSizeMB:DiskStore使用的磁盘大小,默认值30MB。每个cache使用各自的DiskStore。
memoryStoreEvictionPolicy:如果内存中数据超过内存限制,向磁盘缓存时的策略。默认值LRU,可选FIFO、LFU。
 
3、在实体类的映射文件中配置缓存同步策略,如:<cache useage="read-only"/>
    Hibernate提供了四种缓存同步策略:
read-only:只读缓存策略
read-write:读/写缓存策略
nonstrict-read-write:不严格的读/写缓存策略。如果程序对并发数据修改要求不是非常严格,只是偶尔需要更新数据或者两个事务同时更新一条记录的可能性很小,可以采用本选项,获得较好的性能
transactional:事务型缓存策略,发生异常的时候,缓存也能够回滚
 
以上,二级缓存配置就完成了,执行下面代码测试一下二级缓存:
List<Student> list = studentDao.findAll();Session session = HibernateSessionFactory.getSession();session.get(Student.class, 1);session.close();session = HibernateSessionFactory.getSession();session.get(Student.class, 1);session.close();
查看SQL执行情况:
Hibernate: select student0_.id as id0_, student0_.name as name0_, student0_.age as age0_, student0_.grade_id as grade4_0_ from school.student student0_
只有一条获取所有记录的查询语句,并且,我们知道在session关闭之后,Session缓存是被清空的,所以两次执行session.get()都是从二级缓存获取的数据。
 
对于二级缓存,仍然有其弊端存在:
首先,使用条件查询,或者返回所有结果的查询不会使用二级缓存。
其次,二级缓存适合更新频率较低的数据,对于频繁更新的数据不适合使用二级缓存,缓存这样的数据反而会影响性能。可悲的是,这样的数据正是关乎我们核心业务逻辑的数据,也就是说二级缓存对我们的核心业务帮助不大。
最值得注意的是,使用二级缓存时,所有数据操作必须通过hibernate,否则缓存无法做到同步更新,使缓存数据与实际数据库中出现不一致的情况。而实际应用中,难免会有直接的数据库操作,或者使用jdbc处理错误数据或批量更新等。
 
总结
合理使用Hibernate缓存,可以提升Hibernate的性能。而对于整个应用的性能提升,我们首先应该找出性能瓶颈,再做相应的处理,比如增加数据库索引,优化业务逻辑等。当然,也可以对整个应用设置缓存,这时通常的做法并非是考虑数据库级的缓存,而是尽量从最高的层面设置缓存。比如,可以使用页面静态化,可以在Web层使用分布式缓存策略。分布式缓存大多是用Json+Memcached,现在我更偏爱Redis多一些。
所以,对于Hibernate缓存,要合理使用,不要过分依赖。

Hibernate缓存示例:http://download.csdn.net/detail/boyazuo/5117456
原创粉丝点击