1 启用 Hibernate 二级缓存
Hibernate 二级缓存分为两部分, class 缓存和查询缓存,其获取对象的方式有所不同,但两者也有联系,查询缓存必须以 class 缓存为基础才能起作用,否则只会使效率更低。
我们这里使用的二级缓存是通过 ehcache 第三方插件实现的。
1.1 配置 Hibernate.cfg.xml
启用 class 缓存:
<property name="hibernate.cache.provider_class"> org.hibernate.cache.EhCacheProvider </property>
启用查询缓存:
<property name="hibernate.cache.use_query_cache">true</property>
1.2 配置 Spring 框架中的 hibernate
启用 class 缓存:
<prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</prop>
启用查询缓存:
<prop key="hibernate.cache.use_query_cache">true</prop>
1.3 配置 ehcache
Ehcache 配置文件为 ehcache.xml ,默认配置为:
<ehcache>
<diskStore path="java.io.tmpdir"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
overflowToDisk="true"
/>
</ehcache>
其中各项内容的含义为:
1 diskStore :代表当二级缓存对象数据在内存中溢出,如果需要写入文件系统时的文件目录。
2 defaultCache :默认的 calss 缓存配置,如果某个对象没有其专有的配置时, ehcache 一律启用默认配置。
3 maxElementInMemory :对象在内存中可存放的最大数量。
4 eternal :表示对象永不过期,如果选 true 则 5 , 6 两项无效。
5 timeToIdleSeconds :对象的空闲状态过期时间,单位为秒, 0 为可以无限制空闲。
6 timeToLiveSeconds :对象存在的最长时间,单位为秒(注意,如果该项比 5 项要小,则第 5 项无意义), 0 为永不过期。
7 overflowToDisk :当对象在内存中的数量超过 maxElementInMemory 值时,如果该项为 true ,则 ehcahe 会把对象数据写入 diskStore 项指定的目录。
如果需要对某个具体对象进行单独配置时,可以加上一组 cache 配置,例如:
<cache name="com.juyee.mp.bean.SysCodelist" maxElementsInMemory="10000" eternal="true" timeToIdleSeconds="1800" timeToLiveSeconds="0" overflowToDisk="true" />
另外还有两个特殊的 cache 配置:
<cache name="org.hibernate.cache.UpdateTimestampsCache" maxElementsInMemory="5000" eternal="true" timeToIdleSeconds="1800" timeToLiveSeconds="0" overflowToDisk="true"/> <cache name="org.hibernate.cache.StandardQueryCache" maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="1800" timeToLiveSeconds="0" overflowToDisk="true"/>
这两个 cache 配置对应查询缓存,具体作用如下(摘用别人的描述):
“当 hibernate 更新数据库的时候,它怎么知道更新哪些查询缓存呢? hibernate 在一个地方维护每个表的最后更新时间,其实也就是放在上面 UpdateTimestampsCache 所指定的缓存配置里面。
当通过 hibernate 更新的时候, hibernate 会知道这次更新影响了哪些表。然后它更新这些表的最后更新时间。每个缓存都有一个生成时间和这个缓存所查询的表,当 hibernate 查询一个缓存是否存在的时候,如果缓存存在,它还要取出缓存的生成时间和这个缓存所查询的表,然后去查找这些表的最后更新时间,如果有一个表在生成时间后更新过了,那么这个缓存是无效的。
可以看出,只要更新过一个表,那么凡是涉及到这个表的查询缓存就失效了,因此查询缓存的命中率可能会比较低。”
当然,如果没有这两个配置,则 ehcache 将为查询缓存启用默认配置。
2 如何使用 class 缓存
2.1 对象缓存
Class 缓存的作用主要是在内存中保存某个具体对象,当用户第一次通过 get 、 iterator 方式取出对象时,系统会先从 class 缓存中去找,如果没有再通过 sql 语句去数据库中查找相关记录,并将查询到的对象放入内存中。
实例:对某个对象使用二级缓存,只需要在该对象的 hbm 文件中配置即可
<cache usage="read-write"/>
我们注意到其中 usage 这个选项,它有多个选择,对应不同的含义,经常有这两种种:
1 read-only :只对缓存中的对象进行读操作。
2 read-write :当对象被 update 时,缓存中和数据库中一同被修改(缓存不支持事务回滚)。
2.2 关联缓存
目前我们仅仅实现了对一个对象的缓存,那如何对该对象的关联对象集合进行缓存呢?
实例:对某个对象的关联对象集合的二级缓存,需要在该对象的 hbm 文件中 set 配置进行修改
<set name="children" lazy="true" order-by="treeid asc"> <cache usage="read-write"/> <key column="PARENTID"/> <one-to-many class="SysCodelist"/> </set>
注意:
1 只对 one-to-many 有效,而且仅仅缓存的是关联对象的 id 集合,如果需要实现完全缓存,则需要对关联的对象也配置成使用二级缓存。
2 集合缓存是独立的,不受关联对象添加、删除的影响,如果要修改集合内容,必须对这个集合本身进行修改,例如: codelist.getChildren().add() 。
3 如何使用查询缓存
查询缓存,目的是为了将通过 list() 方法的查询结果存入缓存中,并实现对语句的缓存,如果下次用户在使用这条语句进行查询时,将直接从缓存中获取对象数据。
这里要注意的是,查询缓存必须配合 class 缓存使用,如果只启用查询缓存,不对查询对象启用二级缓存,则会大大降低查询效率。
因为,当第一次通过启用查询缓存的 session 进行语句查询时,系统只执行一次数据库查询将所有的记录取出,并将对象存入 class 缓存,语句及 id 集合存入查询缓存;而当用户第二次查询该语句时,系统将先执行去查询缓存中查找,取出所有符合条件的 id 集合,如果这时候该对象的 class 缓存没启用或在 class 缓存中已过期,系统将根据 id ,一个个去数据库 load ,实际上是进行了 1+N 次查询。
实际上,在我们系统中,并不是对所有对象都要进行二级缓存,而 spring 框架中提供的 hibernate 方法,虽然有 getHibernateTemplate().setCacheQueries() ,但该方法影响的是全局的配置,一旦启用,将会对不需要缓存的查询造成不良影响。
于是,我自己在 hibernateService() 中添加了一个方法:
public List findByCachedQuery(final String hql)
{
return (List) getHibernateTemplate().execute(new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException {
Query queryObject = session.createQuery(hql);
queryObject.setCacheable(true);
if (getHibernateTemplate().getQueryCacheRegion() != null) {
queryObject.setCacheRegion(getHibernateTemplate().getQueryCacheRegion());
}
return queryObject.list();
}
}, true);
}
这样,将只在 session 范围内启用查询缓存,一旦该 session 结束了,那么查询缓存也将回复默认配置。
注意:使用时只支持 hql 。
4 注意事项
在使用二级缓存时,注意,所有对数据库的修改都必须走hibernate,如果从其他系统来或使用sql语句来修改数据库相关记录,那么将对二级缓存的数据不会造成影响,换句话说,缓存中的对象数据将和数据库中的不一致。