名扬数据:Hibernate缓存何时使用和如何使用
1. 关于hibern缓存的问题:
1.1. 基本的缓存原理
Hibern缓存分为二级,
第一级存放于session中称为一级缓存,默认带有且不能卸载。
第二级是由sessionFactori控制的进程级缓存。全局共享的缓存,凡是会调用二级缓存的查询方法 都会从中受益。只有经正确的配置后二级缓存才会发挥作用。同时在进行条件查询时必需使用相应的方法才干从缓存中获取数据。比方Query.iter方法、loadget方法等。必需注意的session.find方法永远是从数据库中获取数据,不会从二级缓存中获取数据,即便其中有其所需要的数据也是如此。
查询时使用缓存的实现过程为:首先查询一级缓存中是否具有需要的数据,如果没有,查询二级缓存,如果二级缓存中也没有,此时再执行查询数据库的工作。要注意的此3种方式的查询速度是依次降低的
1.2. 存在问题
1.2.1. 一级缓存的问题以及使用二级缓存的原因
因为Session生命期往往很短,存在于Session内部的第一级最快缓存的生命期当然也很短,所以第一级缓存的命中率是很低的其对系统性能的改善也是很有限的当然,这个Session内部缓存的主要作用是坚持Session内部数据状态同步。并非是hibern为了大幅提高系统性能所提供的
为了提高使用hibern性能,除了惯例的一些需要注意的方法比方:
使用延迟加载、迫切外连接、查询过滤等以外,还需要配置hibern二级缓存。其对系统整体性能的改善往往具有立竿见影的效果!
经过自己以前作项目的经验,一般会有3~4倍的性能提高)
1.2.2. N+1次查询的问题
1.2.2.1 什么时候会遇到1+N问题?
前提:Hibern默认表与表的关联方法是fetch="select"不是fetch="join",这都是为了懒加载而准备的
1一对多(<set><list> 1这方,通过1条sql查找得到1个对象,由于关联的存在 那么又需要将这个对象关联的集合取出,所以合集数量是n还要发出n条sql于是原本的1条sql查询变成了1 +n条
2多对一<many-to-one> 多的这方,通过1条sql查询得到n个对象,由于关联的存在,也会将这n个对象对应的1 方的对象取出, 于是原本的1条sql查询变成了1 +n条
3iterator 查询时,一定先去缓存中找(1条sql查集合,只查出ID没命中时,会再按ID库中逐一查找, 发生1+n条SQL
1.2.2.2 怎么解决1+N 问题?
1 lazy=tru hibernate3开始已经默认是lazy=trulazy=tru时不会立刻查询关联对象,只有当需要关联对象(访问其属性,非id字段)时才会发生查询动作。
2使用二级缓存, 二级缓存的应用将不怕1+N 问题,因为即使第一次查询很慢(未命中)以后查询直接缓存命中也是很快的刚好又利用了1+N
3 当然你也可以设定fetch="join"一次关联表全查出来,但失去了懒加载的特性。
执行条件查询时,iter方法具有著名的 n+1次查询的问题,也就是说在第一次查询时iter方法会执行满足条件的查询结果数再加一次(n+1查询。但是此问题只存在于第一次查询时,后面执行相同查询时性能会得到极大的改善。此方法适合于查询数据量较大的业务数据。
但是注意:当数据量特别大时(比如流水线数据等)需要针对此耐久化对象配置其具体的缓存策略,比如设置其存在于缓存中的最大记录数、缓存存在时间等参数,以防止系统将大量的数据同时装载入内存中引起内存资源的迅速耗尽,反而降低系统的性能!
1.3. 使用hibern二级缓存的其他注意事项:
1.3.1. 关于数据的有效性
另外,hibern会自行维护二级缓存中的数据,以保证缓存中的数据和数据库中的真实数据的一致性!无论何时,当你调用saveupdat或 saveOrUpd方法传送一个对象时,或使用load getlistiter 或scroll方法获得一个对象时, 该对象都将被加入到Session内部缓存中。 当随后flush方法被调用时,对象的状态会和数据库取得同步。
也就是说删除、更新、增加数据的时候,同时更新缓存。当然这也包括二级缓存!
只要是调用hibernate A PI执行数据库相关的工作。hibern都会为你自动保证 缓存数据的有效性!
但是如果你使用了JDBC绕过hibern直接执行对数据库的操作。此时,Hibern不会/也不可能自行感知到数据库被进行的变化改动,也就不能再保证缓存中数据的有效性!
这也是所有的ORM产品共同具有的问题。幸运的Hibern为我表露了Cach清除方法,这给我提供了一个手动保证数据有效性的机会!
一级缓存,二级缓存都有相应的清除方法。
其中二级缓存提供的清除方法为:
按对象class清空缓存
按对象class和对象的主键id清空缓存
清空对象的集合中的缓存数据等。
1.3.2. 适合使用的情况
并非所有的情况都适合于使用二级缓存,需要根据具体情况来决定。同时可以针对某一个耐久化对象配置其具体的缓存策略。
适合于使用二级缓存的情况:
1数据不会被第三方修改;
一般情况下,会被hibern以外修改的数据最好不要配置二级缓存,以免引起不一致的数据。但是如果此数据因为性能的原因需要被缓存,同时又有可能被第3方比如SQL修改,也可以为其配置二级缓存。只是此时需要在sql执行修改后手动调用cach清除方法。以保证数据的一致性
2数据大小在可接收范围之内;
如果数据表数据量特别巨大,此时不适合于二级缓存。原因是缓存的数据量过大可能会引起内存资源紧张,反而降低性能。
如果数据表数据量特别巨大,但是经常使用的往往只是较新的那部分数据。此时,也可为其配置二级缓存。但是必需单独配置其持久化类的缓存策略,比如最大缓存数、缓存过期时间等,将这些参数降低至一个合理的范围(太高会引起内存资源紧张,太低了缓存的意义不大)
3数据更新频率低;
对于数据更新频率过高的数据,频繁同步缓存中数据的代价可能和 查询缓存中的数据从中获得的好处相当,坏处益处相抵消。此时缓存的意义也不大。
4非关键数据(不是财务数据等)
财务数据等是非常重要的数据,绝对不允许出现或使用无效的数据,所以此时为了平安起见最好不要使用二级缓存。
因为此时 正确性”重要性远远大于 高性能”重要性。
2. 目前系统中使用hibern缓存的建议
2.1. 目前情况
一般系统中有三种情况会绕开hibern执行数据库操作:
1多个应用系统同时访问一个数据库
此种情况使用hibern二级缓存会不可避免的造成数据不一致的问题,此时要进行详细的设计。比方在设计上防止对同一数据表的同时的写入操作,
使用数据库各种级别的锁定机制等。
2动态表相关
所谓“动态表”指在系统运行时根据用户的操作系统自动建立的数据表。
比方“自定义表单”等属于用户自定义扩展开发性质的功能模块,因为此时数据表是运行时建立的所以不能进行hibern映射。因此对它操作只能是绕开hibern直接数据库JDBC操作。
如果此时动态表中的数据没有设计缓存,就不存在数据不一致的问题。
如果此时自行设计了缓存机制,则调用自己的缓存同步方法即可。
3使用sql对hibern耐久化对象表进行批量删除时
此时执行批量删除后,缓存中会存在已被删除的数据。
分析:
当执行了第3条(sql批量删除)后,后续的查询只可能是以下三种方式:
a. session.find方法:
根据前面的总结,find方法不会查询二级缓存的数据,而是直接查询数据库。
所以不存在数据有效性的问题。
b. 调用iter方法执行条件查询时:
根据iter查询方法的执行方式,其每次都会到数据库中查询满足条件的id值,然后再根据此id 缓存中获取数据,当缓存中没有此id数据才会执行数据库查询;
如果此记录已被sql直接删除,则iter执行id查询时不会将此id查询进去。所以,即便缓存中有此条记录也不会被客户获得,也就不存在不一致的情况。此情况经过测试验证)
c. 用get或load方法按id执行查询:
客观上此时会查询得到已过期的数据。但是又因为系统中执行sql批量删除一般是针对中间关联数据表,对于中间关联表的查询一般都是采用条件查询 ,按id来查询某一条关联关系的几率很低,所以此问题也不存在!
如果某个值对象确实需要按id查询一条关联关系,同时又因为数据量大使用 sql执行批量删除。当满足此两个条件时,为了保证按id 查询得到正确的结果,可以使用手动清楚二级缓存中此对象的数据的方法!!此种情况出现的可能性较小)
2.2. 建 议
1建议不要使用sql直接执行数据耐久化对象的数据的更新,但是可以执行 批量删除。系统中需要批量更新的地方也较少)
2如果必需使用sql执行数据的更新,必需清空此对象的缓存数据。调用
SessionFactory.evictclass
SessionFactory.evictclass,id等方法。
3批量删除数据量不大的时候可以直接采用hibern批量删除,这样就不存在绕开hibern执行sql发生的缓存数据一致性的问题。
4不推荐采用hibern批量删除方法来删除大批量的记录数据。
原因是hibern批量删除会执行1条查询语句外加 满足条件的n条删除语句。而不是一次执行一条条件删除语句!
当待删除的数据很多时会有很大的性能瓶颈!如果批量删除数据量较大,比如超越50条,可以采用JDBC直接删除。这样作的好处是只执行一条sql删除语句,性能会有很大的改善。同时,缓存数据同步的问题,可以采用 hibern清除二级缓存中的相关数据的方法。
调 用
SessionFactory.evictclass ;
SessionFactory.evictclass,id等方法。
所以说,对于一般的应用系统开发而言(不涉及到集群,分布式数据同步问题等)因为只在中间关联表执行批量删除时调用了sql执行,同时中间关联表一般是执行条件查询不太可能执行按id查询。所以,此时可以直接执行sql删除,甚至不需要调用缓存的清除方法。这样做不会导致以后配置了二级缓存引起数据有效性的问题。
退一步说,即使以后真的调用了按id查询中间表对象的方法,也可以通过调用清除缓存的方法来解决。
3具体的配置方法
根据我解的很多hibern使用者在调用其相应方法时都迷信的相信“hibern会自行为我处置性能的问题”或者“hibernate 会自动为我所有操作调用缓存”,实际的情况是hibern虽然为我提供了很好的缓存机制和扩展缓存框架的支持,但是必需经过正确的调用其才有可能发挥作用!所以造成很多使用hibern系统的性能问题,实际上并不是hibern不行或者不好,而是因为使用者没有正确的解其使用方法造成的相反,如果配置得当hibern性能表示会让你有相当“惊喜的发现。下面我讲解具体的配置方法。
ibern提供了二级缓存的接口:
net.sf.hibernate.cache.Provider,
同时提供了一个默认的 实现net.sf.hibernate.cache.HashtableCacheProvid
也可以配置 其他实现 比方ehcache,jbosscach等。
具体的配置位置位于hibernate.cfg.xml文件中
<property name="hibernate.cache.use_query_cache">true</property>
<property name="hibernate.cache.provider_class">net.sf.hibernate.cache.HashtableCacheProvider</property>
很多的hibern使用者在 配置到 这一步 就以为 完事了
注意:其实光这样配,根本就没有使用hibern二级缓存。同时因为他使用hibern时大多时候是马上关闭session所以,一级缓存也没有起到任何作用。结果就是没有使用任何缓存,所有的hibern操作都是直接操作的数据库!性能可以想见。
正确的方法是除了以上的配置外还应该配置每一个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="NA ME" 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.iter方法时才会调缓存的数据。
同时 get 和 load方法 都会查询缓存中的数据
对于不同的缓存框架具体的配置方法会有不同,但是大体是以上的配置