[转] 关于hibernate的缓存使用

http://blog.csdn.net/woshichenxu/article/details/586361java

 

1.     关于hibernate缓存的问题:

1.1.1.         基本的缓存原理

Hibernate缓存分为二级,第一级存放于session中称为一级缓存,默认带有且不能卸载。sql

 

第二级是由sessionFactory控制的进程级缓存。是全局共享的缓存,凡是会调用二级缓存的查询方法都会从中受益。只有经正确的配置后二级缓存才会发挥做用。同时在进行条件查询时必须使用相应的方法才能从缓存中获取数据。好比Query.iterate()方法、loadget方法等。必须注意的是session.find方法永远是从数据库中获取数据,不会从二级缓存中获取数据,即使其中有其所须要的数据也是如此。数据库

 

查询时使用缓存的实现过程为:首先查询一级缓存中是否具备须要的数据,若是没有,查询二级缓存,若是二级缓存中也没有,此时再执行查询数据库的工做。要注意的是:此3种方式的查询速度是依次下降的。缓存

1.2.   存在的问题

1.2.1.      一级缓存的问题以及使用二级缓存的缘由

     由于Session的生命期每每很短,存在于Session内部的第一级最快缓存的生命期固然也很短,因此第一级缓存的命中率是很低的。其对系统性能的改善也是颇有限的。固然,这个Session内部缓存的主要做用是保持Session内部数据状态同步。并不是是hibernate为了大幅提升系统性能所提供的。安全

为了提升使用hibernate的性能,除了常规的一些须要注意的方法好比:session

使用延迟加载、迫切外链接、查询过滤等之外,还须要配置hibernate的二级缓存。其对系统总体性能的改善每每具备立竿见影的效果!app

(通过本身之前做项目的经验,通常会有3~4倍的性能提升)框架

 

1.2.2.      N+1次查询的问题

执行条件查询时,iterate()方法具备著名的“n+1”次查询的问题,也就是说在第一次查询时iterate方法会执行知足条件的查询结果数再加一次(n+1)的查询。可是此问题只存在于第一次查询时,在后面执行相同查询时性能会获得极大的改善。此方法适合于查询数据量较大的业务数据。分布式

可是注意:当数据量特别大时(好比流水线数据等)须要针对此持久化对象配置其具体的缓存策略,好比设置其存在于缓存中的最大记录数、缓存存在的时间等参数,以免系统将大量的数据同时装载入内存中引发内存资源的迅速耗尽,反而下降系统的性能!!!ide

 

1.3.   使用hibernate二级缓存的其余注意事项:

1.3.1.      关于数据的有效性

另外,hibernate会自行维护二级缓存中的数据,以保证缓存中的数据和数据库中的真实数据的一致性!不管什么时候,当你调用save()update() saveOrUpdate()方法传递一个对象时,或使用load() get()list()iterate() scroll()方法得到一个对象时该对象都将被加入到Session的内部缓存中。 当随后flush()方法被调用时,对象的状态会和数据库取得同步。

 

也就是说删除、更新、增长数据的时候,同时更新缓存。固然这也包括二级缓存!

 

只要是调用hibernate API执行数据库相关的工做。hibernate都会为你自动保证缓存数据的有效性!!

 

可是,若是你使用了JDBC绕过hibernate直接执行对数据库的操做。此时,Hibernate不会/也不可能自行感知到数据库被进行的变化改动,也就不能再保证缓存中数据的有效性!!

 

这也是全部的ORM产品共同具备的问题。幸运的是,Hibernate为咱们暴露了Cache的清除方法,这给咱们提供了一个手动保证数据有效性的机会!!

一级缓存,二级缓存都有相应的清除方法。

 

其中二级缓存提供的清除方法为:

按对象class清空缓存

                按对象class和对象的主键id清空缓存

                清空对象的集合中的缓存数据等。

   

1.3.2.      适合使用的状况

并不是全部的状况都适合于使用二级缓存,须要根据具体状况来决定。同时能够针对某一个持久化对象配置其具体的缓存策略。

 

适合于使用二级缓存的状况:

一、数据不会被第三方修改;

 

通常状况下,会被hibernate之外修改的数据最好不要配置二级缓存,以避免引发不一致的数据。可是若是此数据由于性能的缘由须要被缓存,同时又有可能被第3方好比SQL修改,也能够为其配置二级缓存。只是此时须要在sql执行修改后手动调用cache的清除方法。以保证数据的一致性

 

  2、数据大小在可接收范围以内;

 

     若是数据表数据量特别巨大,此时不适合于二级缓存。缘由是缓存的数据量过大可能会引发内存资源紧张,反而下降性能。

 

若是数据表数据量特别巨大,可是常用的每每只是较新的那部分数据。此时,也可为其配置二级缓存。可是必须单独配置其持久化类的缓存策略,好比最大缓存数、缓存过时时间等,将这些参数下降至一个合理的范围(过高会引发内存资源紧张,过低了缓存的意义不大)。

 

  3、数据更新频率低;

 

     对于数据更新频率太高的数据,频繁同步缓存中数据的代价可能和查询缓存中的数据从中得到的好处至关,坏处益处相抵消。此时缓存的意义也不大。

 

 

  4、非关键数据(不是财务数据等)

 

  财务数据等是很是重要的数据,绝对不容许出现或使用无效的数据,因此此时为了安全起见最好不要使用二级缓存。

  由于此时“正确性”的重要性远远大于“高性能”的重要性。

 

2.     目前系统中使用hibernate缓存的建议

1.4.   目前状况

 通常系统中有三种状况会绕开hibernate执行数据库操做:

一、多个应用系统同时访问一个数据库

   此种状况使用hibernate二级缓存会不可避免的形成数据不一致的问题,

   此时要进行详细的设计。好比在设计上避免对同一数据表的同时的写入操做,

   使用数据库各类级别的锁定机制等。

 

2、动态表相关

   所谓“动态表”是指在系统运行时根据用户的操做系统自动创建的数据表。

   好比“自定义表单”等属于用户自定义扩展开发性质的功能模块,由于此时数据表是运行时创建的,因此不能进行hibernate的映射。所以对它的操做只能是绕开hibernate的直接数据库JDBC操做。

      若是此时动态表中的数据没有设计缓存,就不存在数据不一致的问题。

   若是此时自行设计了缓存机制,则调用本身的缓存同步方法便可。

 

3、使用sql对hibernate持久化对象表进行批量删除时

     此时执行批量删除后,缓存中会存在已被删除的数据。

分析: 

   当执行了第3条(sql批量删除)后,后续的查询只多是如下三种方式:

a. session.find()方法:

根据前面的总结,find方法不会查询二级缓存的数据,而是直接查询数据库。

因此不存在数据有效性的问题。

b. 调用iterate方法执行条件查询时:

根据iterate查询方法的执行方式,其每次都会到数据库中查询知足条件的id值,而后再根据此id 到缓存中获取数据,当缓存中没有此id的数据才会执行数据库查询;

若是此记录已被sql直接删除,则iterate在执行id查询时不会将此id查询出来。因此,即使缓存中有此条记录也不会被客户得到,也就不存在不一致的状况。(此状况通过测试验证)

 

c. getload方法按id执行查询:

 

客观上此时会查询获得已过时的数据。可是又由于系统中执行sql批量删除通常是

针对中间关联数据表,对于

中间关联表的查询通常都是采用条件查询 ,id来查询某一条关联关系的概率很低,因此此问题也不存在!

 

   若是某个值对象确实须要按id查询一条关联关系,同时又由于数据量大使用了sql执行批量删除。当知足此两个条件时,为了保证按id 的查询获得正确的结果,可使用手动清楚二级缓存中此对象的数据的方法!!

(此种状况出现的可能性较小)

 

1.5.   建议

1、建议不要使用sql直接执行数据持久化对象的数据的更新,可是能够执行批量删除。(系统中须要批量更新的地方也较少)

 

2、若是必须使用sql执行数据的更新,必须清空此对象的缓存数据。调用

SessionFactory.evict(class)

SessionFactory.evict(class,id)

等方法。

 

3、在批量删除数据量不大的时候能够直接采用hibernate的批量删除,这样就不存在绕开hibernate执行sql产生的缓存数据一致性的问题。

 

4、不推荐采用hibernate的批量删除方法来删除大批量的记录数据。

缘由是hibernate的批量删除会执行1条查询语句外加知足条件的n条删除语句。而不是一次执行一条条件删除语句!!

当待删除的数据不少时会有很大的性能瓶颈!!!若是批量删除数据量较大,好比超过50,能够采用JDBC直接删除。这样做的好处是只执行一条sql删除语句,性能会有很大的改善。同时,缓存数据同步的问题,能够采用 hibernate清除二级缓存中的相关数据的方法。

调用 SessionFactory.evict(class) SessionFactory.evict(class,id)等方法。

 

因此说,对于通常的应用系统开发而言(不涉及到集群,分布式数据同步问题等),由于只在中间关联表执行批量删除时调用了sql执行,同时中间关联表通常是执行条件查询不太可能执行按id查询。因此,此时能够直接执行sql删除,甚至不须要调用缓存的清除方法。这样作不会致使之后配置了二级缓存引发数据有效性的问题。

 

退一步说,即便之后真的调用了按id查询中间表对象的方法,也能够经过调用清除缓存的方法来解决。

 

四、具体的配置方法 

根 据我了解的不少hibernate的使用者在调用其相应方法时都迷信的相信“hibernate会自行为咱们处理性能的问题”,或者“hibernate 会自动为咱们的全部操做调用缓存”,实际的状况是hibernate虽然为咱们提供了很好的缓存机制和扩展缓存框架的支持,可是必须通过正确的调用其才有 可能发挥做用!!因此形成不少使用hibernate的系统的性能问题,实际上并非hibernate不行或者很差,而是由于使用者没有正确的了解其使 用方法形成的。相反,若是配置得当hibernate的性能表现会让你有至关“惊喜的”发现。下面我讲解具体的配置方法.

 ibernate提供了二级缓存的接口:
net.sf.hibernate.cache.Provider,
同时提供了一个默认的 实现net.sf.hibernate.cache.HashtableCacheProvider,
也能够配置 其余的实现 好比ehcache,jbosscache等。

具体的配置位置位于hibernate.cfg.xml文件中
<property name="hibernate.cache.use_query_cache">true</property>
<property name="hibernate.cache.provider_class">net.sf.hibernate.cache.HashtableCacheProvider</property>

不少的hibernate使用者在 配置到 这一步 就觉得 完事了,
注 意:其实光这样配,根本 就没有使用hibernate的二级缓存。同时由于他们在使用hibernate时大多时候是立刻关闭session,因此,一级缓存也没有起到任何做 用。结果就是没有使用任何缓存,全部的hibernate操做都是直接操做的数据库!!性能能够想见。

正确的办法是除了以上的配置外还应该配置每个vo对象的具体缓存策略,在影射文件中配置。例如:

<hibernate-mapping>
<class name="com.sobey.sbm.model.entitySystem.vo.DataTypeVO" table="dcm_datatype">
<cache usage="read-write"/>
<id name="id" column="TYPEID" type="java.lang.Long">
<generator class="sequence"/>
</id>

<property name="name" column="NAME" type="java.lang.String"/>
<property name="dbType" column="DBTYPE" type="java.lang.String"/>
</class>
</hibernate-mapping>


关键就是这个<cache usage="read-write"/>,其有几个选择
read-only,read-write,transactional,等
而后在执行查询时 注意了 ,若是是条件查询,或者返回全部结果的查询,此时session.find()方法 不会获取缓存中的数据。只有调用query.iterate()方法时才会调缓存的数据。

同时 get 和 load方法 是都会查询缓存中的数据 .

对于不一样的缓存框架具体的配置方法会有不一样,可是大致是以上的配置

(另外,对于支持事务型,以及支持集群的环境的配置我会争取在后续的文章中中 发表出来)

 

3.     总结

总之是根据不一样的业务状况和项目状况对hibernate进行有效的配置和正确的使用,扬长避短。不存在适合于任何状况的一个“万能”的方案。

以上结论及建议均创建在本身在对 Hibernate 2.1.2中的测试结果以及之前的项目经验的基础上。若有谬处,请打家提出指正:)!

最后,祝你们 新年快乐!!在新的一年里 取得人生的进步!!!

相关文章
相关标签/搜索