Hibernate缓存机制
缓存是介于应用程序和物理数据源之间,其作用是为了降低应用程序对物理数据源访问的频次,从而提高了应用的运行性能。缓存内的数据是对物理数据源中的数据的复制,应用程序在运行时从缓存读写数据,在特定的时刻或事件会同步缓存和物理数据源的数据。
基本信息
缓存的介质一般是内存,所以读写速度很快。但如果缓存中存放的数据量非常大时,也会用硬盘作为缓存介质。缓存的实现不仅仅要考虑存储的介质,还要考虑到管理缓存的并发访问和缓存数据的生命周期。
Hibernate的缓存包括Session的缓存和SessionFactory的缓存,其中SessionFactory的缓存又可以分为两类:内置缓存和外置缓存。Session的缓存是内置的,不能被卸载,也被称为Hibernate的第一级缓存。SessionFactory的内置缓存和Session的缓存在实现方式上比较相似,前者是SessionFactory对象的一些集合属性包含的数据,后者是指Session的一些集合属性包含的数据。SessionFactory的内置缓存中存放了映射元数据和预定义SQL语句,映射元数据是映射文件中数据的拷贝,而预定义SQL语句是在Hibernate初始化阶段根据映射元数据推导出来,SessionFactory的内置缓存是只读的,应用程序不能修改缓存中的映射元数据和预定义SQL语句,因此SessionFactory不需要进行内置缓存与映射文件的同步。SessionFactory的外置缓存是一个可配置的插件。在默认情况下,SessionFactory不会启用这个插件。外置缓存的数据是数据库数据的拷贝,外置缓存的介质可以是内存或者硬盘。SessionFactory的外置缓存也被称为Hibernate的第二级缓存。
Hibernate的这两级缓存都位于持久化层,存放的都是数据库数据的拷贝,那么它们之间的区别是什么呢?为了理解二者的区别,需要深入理解持久化层的缓存的两个特性:缓存的范围和缓存的并发访问策略。
什么样的数据适合存放到第二级缓存中?
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都要从缓存中清空。
缓存同步策略:
缓存同步策略决定了数据对象在缓存中的存取规则,我们必须为每个实体类指定相应的缓存同步策略.Hibernate中提供了4种不同的缓存同步策略: 1.read-only:只读.对于不会发生改变的数据可使用. 2.nonstrict-read-write:如果程序对并发访问下的数据同步要求不严格,且数据更新频率较低,采用本缓存同步策略可获得较好性能. 3.read-write:严格的读写缓存.基于时间戳判定机制,实现了"read committed"事务隔离等级.用于对数据同步要求的情况,但不支持分布式缓存,实际应用中使用最多的缓存同步策略.
4.transactional: 事务型缓存,必须运行在JTA事务环境中.此缓存中,缓存的相关操作被添加到事务中(此缓存类似于一个内存数据库),如事务失败,则缓冲池的数据会一同回 滚到事务的开始之前的状态.事务型缓存实现了"Repeatable read"事务隔离等级,有效保证了数据的合法性,适应于对关键数据的缓存,Hibernate内置缓存中,只有JBossCache支持事务型缓存.
如何配置hibernate cache呢?
在applicationContext.xml文件中添加以下代码: <prop key="hibernate.cache.use_second_level_cache">true</prop> <!--设置缓存机制为二级缓存 --> <prop key="hibernate.cache.use_query_cache">true</prop> <!--启动查询缓存 --> <prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</prop> <!--设置二级缓存的Provider类 --> <prop key="hibernate.cache.provider_configuration_file_resource_path">WEB-INF/classes/ehcache.xml</prop> <!--设置缓存的配置文件路径 -->
将ehcache.xml文件放到src下面,并配置ehcache.xml文件如下:
<ehcache> <diskStore path="D:cache" />
<!-- maxElementsInMemory为缓存对象的最大数目, eternal设置是否永远不过期,timeToIdleSeconds对象处于空闲状态的最多秒数,timeToLiveSeconds对象处于缓存状态的最多秒数 -->
<defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="3600" overflowToDisk="true" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU" /> <cache name="net.nk.entity.DataAttr" maxElementsInMemory="800" eternal="false" overflowToDisk="false" timeToIdleSeconds="3600" timeToLiveSeconds="3600" /> <cache name="net.nk.entity.SmContentCategory" maxElementsInMemory="800" eternal="false" overflowToDisk="false" timeToIdleSeconds="3600" timeToLiveSeconds="3600" /> </ehcache>
说明:以上是对DataAttr、SmContentCategory两个实体类配置了二级缓存,你也可以继续添加。
在设置了缓存机制的类的xml文件中添加一段代码
<hibernate-mapping> <!--SM_PRODUCT表的hibernate映射描述文件 --> <class name="net.nk.entity.SmProduct" table="SM_PRODUCT" > <cache usage="read-write" region="net.nk.entity.SmProduct"/> <!-- ID --> <id name="id" type="string"> <column name="ID" /> <generator class="assigned" /> </id>
其中类的xml文件中的region设置要和ehcache.xml文件中的保持一致,此处是通过包名+类名的方式。
在调用数据库数据方法时,可采取以下设置:
protected List<POJO> getAll(Class<T> entityClass,boolean iscache) throws SSHException { HibernateTemplate ht = getHibernateTemplate(); if(iscache){ ht.setCacheQueries(true); } return ht.find("from "+entityClass.getName());//getHibernateTemplate().loadAll(entityClass); }
至此,可以通过放开hibernate的show_sql来查看是否缓存机制生效了。
批量处理: 由于Hibernate对这两种缓存有着不同的管理机制,对于二级缓存,我们可以对它的大小进行相关配置,而对于内部缓存,Hibernate就采取了“放任自流”的态度了,对它的容量并没有限制。现在症结找到了,我们做海量数据插入的时候,生成这么多的对象就会被纳入内部缓存(内部缓存是在内存中做缓存的),这样你的系统内存就会一点一点的被蚕食,如果最后系统被挤“炸”了,也就在情理之中了。
我们想想如何较好的处理这个问题呢?有的开发条件又必须使用Hibernate来处理,当然有的项目比较灵活,可以去寻求其他的方法。
这里推荐两种方法: (1):优化Hibernate,程序上采用分段插入及时清除缓存的方法。 (2):绕过Hibernate API ,直接通过 JDBC API 来做批量插入,这个方法性能上是最 好的,也是最快的。 对于上述中的方法1,其基本是思路为:优化Hibernate,在配置文件中设置hibernate.jdbc.batch_size参数,来指定每次提交SQL的数量;程序上采用分段插入及时清除缓存的方法(Session实现了异步write-behind,它允许Hibernate显式地写操作的批处理),也就是每插入一定量的数据后及时的把它们从内部缓存中清除掉,释放占用的内存。
设置hibernate.jdbc.batch_size参数,可参考如下配置。
<hibernate-configuration> <session-factory> ......... <property name=” hibernate.jdbc.batch_size”>50</property> ......... <session-factory> <hibernate-configuration>
配置hibernate.jdbc.batch_size参数的原因就是尽量少读数据库,hibernate.jdbc.batch_size参数值越大,读数据库的次数越少,速度越快。从上面的配置可以看出,Hibernate是等到程序积累到了50个SQL之后再批量提交。
hibernate.jdbc.batch_size参数值也可能不是设置得越大越好,从性能角度上讲还有待商榷。这要考虑实际情况,酌情设置,一般情形设置30、50就可以满足需求了。
Session session=HibernateUtil.currentSession(); Transatcion tx=session.beginTransaction(); for(int i=0;i<10000;i++) ...{ Student st=new Student(); st.setName(“feifei”); session.save(st); if(i%50==0) //以每50个数据作为一个处理单元 ...{ session.flush(); //保持与数据库数据的同步 session.clear(); //清除内部缓存的全部数据,及时释放出占用的内存 } } tx.commit(); .........
在一定的数据规模下,这种做法可以把系统内存资源维持在一个相对稳定的范围。 注意:前面提到二级缓存,在这里有必要再提一下。如果启用了二级缓存,从机制上讲Hibernate为了维护二级缓存,我们在做插入、更新、删除操作时,Hibernate都会往二级缓存充入相应的数据。性能上就会有很大损失,所以建议在批处理情况下禁用二级缓存。
总结: 不要想当然的以为缓存一定能提高性能,仅仅在你能够驾驭它并且条件合适的情况下才是这样的。hibernate的二级缓存限制还是比较多的,不方便用jdbc可能会大大的降低更新性能。在不了解原理的情况下乱用,可能会有1+N的问题。不当的使用还可能导致读出脏数据。
如果受不了hibernate的诸多限制,那么还是自己在应用程序的层面上做缓存吧。 在越高的层面上做缓存,效果就会越好。就好像尽管磁盘有缓存,数据库还是要实现自己的缓存,尽管数据库有缓存,咱们的应用程序还是要做缓存。因为底层的缓存它并不知道高层要用这些数据干什么,只能做的比较通用,而高层可以有针对性的实现缓存,所以在更高的级别上做缓存,效果也要好些吧。
posted on 2013-10-27 01:46 heartstage 阅读(259) 评论(0) 编辑 收藏 举报