如何干预HIBERNATE二级缓存

       此文的目的只是想把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的方法,所以才这样自已弄了一份,如果有高手知道方法,分享一下吧。

你可能感兴趣的:(spring,Hibernate,应用服务器,cache,配置管理)