此文的目的只是想把HIBERNATE缓存和应用的业务缓存集中管理,并不提倡过多干预HIBERNATE的二级缓存,只是希望能在某个特殊的时候能派上用场,当然你也可以从此文了解到HIBERNATE二级缓存的一点细节。本文不讲如何通过Hibernater提供的标准接口来操作缓存,而是直接操作底层的缓存。
由于各种原因,系统缓存可能会出现脏数据,这时你可能不想重启服务器,又能把所有缓存清掉。
一年前,当我发现不时的有新手在项目里面使用了JDBC更新了数年据库,但没有更新HIBERNATE二级缓存,缓存的失效时间配了一天,结果某天出现脏数据后,服务器在用,不能因为这个错误而暂停几千个客户的服务,结果这一天问题就不断的出现,那个郁闷得真不行...,于是就着手把HIBERNATE的缓存也纳进来统一管理。
好,不说费话了,管理二级缓存,要先从HIBERNATE的缓存配置着手,我使用Spring来管理数据源和SessionFactory,下面是SessionFactory Bean的一个属性
<property name="hibernateProperties">
<props>
<!-- 缓存操作提供者实现类,此类只要实现了org.hibernate.cache.CacheProvider就行 -->
<prop key="hibernate.cache.provider_class">
com.esc.cache.OscacheProvider
</prop>
.............
上面的这个com.esc.cache.OscacheProvider类是从com.opensymphony.oscache.hibernate.OSCacheProvider复制过来的,只是改了一两个地方,这个类记录应用启动时,Hibernate初始化二级缓存的过程,下面分析一下源码
package com.esc.cache;
import java.util.Properties;
import org.apache.log4j.Logger;
import org.hibernate.cache.Cache;
import org.hibernate.cache.CacheException;
import org.hibernate.cache.CacheProvider;
import org.hibernate.cache.Timestamper;
import org.hibernate.util.StringHelper;
import com.esc.common.util.SysContext;
import com.opensymphony.oscache.base.CacheEntry;
import com.opensymphony.oscache.base.Config;
import com.opensymphony.oscache.general.GeneralCacheAdministrator;
import com.opensymphony.oscache.util.StringUtil;
public class OscacheProvider implements CacheProvider {
public static final Logger LOG = SysContext.getLogger();
// 这个属性就是在SPRING配置文件中指定缓存配置文件路径的属性名
public static final String OSCACHE_CONFIGURATION_RESOURCE_NAME = "com.opensymphony.oscache.configurationResourceName";
/** The <tt>OSCache</tt> refresh period property suffix. */
public static final String OSCACHE_REFRESH_PERIOD = "refresh.period";
/** The <tt>OSCache</tt> CRON expression property suffix. */
public static final String OSCACHE_CRON = "cron";
// 缓存的操作最终通过此实例进行,
private static GeneralCacheAdministrator cache;
// 应用启动时,首先执行此方法
public void start(Properties hibernateSystemProperties)
throws CacheException {
if (cache == null) {
// construct the cache
LOG.debug("Starting OSCacheProvider...");
// 取得配置文件
String configResourceName = null;
if (hibernateSystemProperties != null) {
configResourceName = (String) hibernateSystemProperties
.get(OSCACHE_CONFIGURATION_RESOURCE_NAME);
}
if (StringUtil.isEmpty(configResourceName)) {
// 如果配置文件没有内容,则使用默认的配置初始化底层缓存实例
cache = new GeneralCacheAdministrator();
} else {//否则就使用用户指定的配置初始化缓存实例
Properties propertiesOSCache = Config.loadProperties(
configResourceName, this.getClass().getName());
cache = new GeneralCacheAdministrator(propertiesOSCache);
}
LOG.debug("OSCacheProvider started.");
} else {
LOG.warn("Tried to restart OSCacheProvider, which is already running.");
}
}
/*执行完start方法后,Hibernate会为每一个配置了二级缓存的实体调用一次本方法,以创建一个它自已缓存操作实例,
此实例实现了org.hibernate.cache.Cache接口,每个实例最终都通过操作全局静态变量cache来读写缓存*/
public Cache buildCache(String region, Properties properties)
throws CacheException {
if (cache != null) {
LOG.debug("building cache in OSCacheProvider...");
String refreshPeriodString = cache.getProperty(StringHelper
.qualify(region, OSCACHE_REFRESH_PERIOD));
int refreshPeriod = refreshPeriodString == null ? CacheEntry.INDEFINITE_EXPIRY
: Integer.parseInt(refreshPeriodString.trim());
String cron = cache.getProperty(StringHelper.qualify(region,
OSCACHE_CRON));
// 此处被修改,OscacheImp类是我自已实现org.hibernate.cache.Cache接口缓存存取类
return new OscacheImp(cache, refreshPeriod, cron, region);
}
throw new CacheException(
"OSCache was stopped or wasn't configured via method start.");
}
/**
* 这是应用正常停止时被调用的方法
*/
public void stop() {
if (cache != null) {
LOG.debug("Stopping OSCacheProvider...");
cache.destroy();
cache = null;
LOG.debug("OSCacheProvider stopped.");
}
}
/**
* 这个方法是我另外加进去的,就是用于取得底层缓存实例,
*/
public static GeneralCacheAdministrator getCache() {
return cache;
}
//下面的两个方法没细看,我就不忽悠了
/**
* @see org.hibernate.cache.CacheProvider#nextTimestamp()
*/
public long nextTimestamp() {
return Timestamper.next();
}
/**
* This method isn't documented in Hibernate:
*
* @see org.hibernate.cache.CacheProvider#isMinimalPutsEnabledByDefault()
*/
public boolean isMinimalPutsEnabledByDefault() {
return false;
}
}
上面我将buildCache方法的返回值改成我自的实现,下面就是这个实现类,这个实现也是从oscache那COPY的,只是加上了一点自已的代码,还改良了一个地方。
package com.esc.cache;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.hibernate.cache.Cache;
import org.hibernate.cache.CacheException;
import org.hibernate.cache.Timestamper;
import com.opensymphony.oscache.base.CacheEntry;
import com.opensymphony.oscache.base.EntryRefreshPolicy;
import com.opensymphony.oscache.base.NeedsRefreshException;
import com.opensymphony.oscache.general.GeneralCacheAdministrator;
import com.esc.common.util.SysContext;
public class OscacheImp implements CacheInterFace,Cache{
/*上文的那个全局静态变量通过构造函数传到这里,本类所有操作都是操作这个属性完成*/
private static GeneralCacheAdministrator cache;
private final int refreshPeriod;
private final String cron;
private final String regionName;
private final String[] regionGroups;
public OscacheImp() {
this(OscacheProvider.getCache(), -1, null, "custumerRegion");
}
public OscacheImp(GeneralCacheAdministrator cache, int refreshPeriod,String cron, String region) {
if(OscacheImp.cache==null){
OscacheImp.cache = cache;
}
this.refreshPeriod = refreshPeriod;
this.cron = cron;
this.regionName = region;
this.regionGroups = new String[] {region};
}
//放入缓存
public void put(Object key, Object value) throws CacheException {
String stringKey = toString(key);
cache.putInCache( stringKey, value, regionGroups );
//我加的,我把HIBERNATE所有缓存的KEY放入了应用的一个LinkedHasMap中,这就是我能从底层干预其缓存的基本条件之一,另一个条件就是我也拿到上文一再提到的全局静态变量cache
List<Object> cacheKeys = SysContext.getCacheKeys();
if (!cacheKeys.contains(stringKey))
cacheKeys.add(stringKey);
}
//从缓存取
public Object get(Object key) throws CacheException {
try {
Object obj = cache.getFromCache( toString(key), refreshPeriod, cron );
if (obj == null) {
SysContext.getCacheKeys().remove(key);
}
return obj;
}
catch (NeedsRefreshException e) {
cache.cancelUpdate( toString(key) );
return null;
}
}
//从缓存移除
public void remove(Object key) throws CacheException {
String stringKey = toString(key);
cache.flushEntry( stringKey );
SysContext.getCacheKeys().remove(key);
}
//这就是上文我说的改良的地方了,原来String+String,哈哈,忽悠了一把
private String toString(Object key) {
return new StringBuilder(String.valueOf(key)).append(".").append(regionName).toString();
}
}
上面已经把底层的cache实例拿到,hibernate放入缓存的KEY也全部拿到,想怎么做就由你了,不过有一点,我那时找了很久,就是没有找到取oscache所有缓存KEY的方法,所以才这样自已弄了一份,如果有高手知道方法,分享一下吧。