论坛首页 Java企业应用论坛

偶来谈谈Hibernte 的QueryCache。

浏览 8593 次
该帖已经被评为精华帖
作者 正文
   发表时间:2005-01-27  
今天一大早回复buaawh的关于hibernate cache的帖子:http://forum.iteye.com/viewtopic.php?t=9706
时,发现自己对于hibernte cache存在一些理解误差,于是回去翻看了一遍hibernte的源代码,下面写出hibernate
的Query Cache部分的分析:
先看看QueryCache的源代码

引用

public void put(QueryKey key, Type[] returnTypes, List result, SessionImplementor session) throws HibernateException {
if ( log.isDebugEnabled() ) log.debug("caching query results in region: " + regionName);
List cacheable = new ArrayList( result.size()+1 );
cacheable.add( new Long( session.getTimestamp() ) ); 1)
for ( int i=0; i<result.size(); i++ ) {
if ( returnTypes.length==1 ) {
cacheable.add( returnTypes[0].disassemble( result.get(i), session ) );
}
else {
cacheable.add( TypeFactory.disassemble( (Object[]) result.get(i), returnTypes, session ) );
}
}
queryCache.put(key, cacheable); 2)
}

红色加亮的部分是核心部分,将查询结果集Result保存到一个List cacheable中,保存的对象是由各自Type desassemble出的对象。
代码中红色加亮的2)说明用一个QueryKey作为key将查询结果集保存到cache中。
值得说明的是,这里的cache是一个接口,通过这个良好设计的接口,hibernate很容易切换不同的cache 实现。
再来看看QueryKey的代码:
	public QueryKey(String queryString, QueryParameters queryParameters) {
		this.sqlQueryString = queryString;
		this.types = queryParameters.getPositionalParameterTypes();
		this.values = queryParameters.getPositionalParameterValues();
		RowSelection selection = queryParameters.getRowSelection();
		if (selection!=null) {
			firstRow = selection.getFirstRow();
			maxRows = selection.getMaxRows();
		}
		else {
			firstRow = null;
			maxRows = null;
		}
		this.namedParameters = queryParameters.getNamedParameters();
	}

可以看出,它包含了一个Query所有的信息。甚至包括firstRow,maxRow。
联想到QueryCache是二级缓存,那么它应该由SessionFactory维护,于是在SessionFactoryImpl中search "QueryCache",
找出如下 与 QueryCache相关的核心代码:
		if ( settings.isQueryCacheEnabled() ) {
			updateTimestampsCache = new UpdateTimestampsCache( settings.getCacheProvider(), properties ); [color=red]1)[/color]
			queryCache = new QueryCache( settings.getCacheProvider(), properties, updateTimestampsCache, null );  [color=red]2)[/color]
			queryCaches = Collections.synchronizedMap( new HashMap() );
		}

settings.isQueryCacheEnabled(),这个应该对应到hibernate.properties中的 use_query_cache true 属性。SessionFacotryImpl中维护着一个updateTimestampsCache实例变量,从1)中可以看出,
它的构造器需要一个CacheProvider 和一个props,这个props有cfg.getProperties()来,应该包含一些与
queryCache相关的配置信息。
我们看一下这个类UpdateTimestampsCache的源代码,
UpdateTimestamplsCache的doc上清楚写道:
引用
Tracks the timestamps of the most recent updates to particular tables.

说明这个类是用来跟踪特定表的最近修改timestamp(时间戳)。它只有一个实例变量:
private Cache updateTimestamps;

可以猜测这个变量是用来保存特定表的最近更新时间戳。
UpdateTimestampsCache有一个核心method:
	public synchronized boolean isUpToDate(Set spaces, Long timestamp) throws HibernateException {
		Iterator iter = spaces.iterator();
		while ( iter.hasNext() ) {
			Serializable space = (Serializable) iter.next();
			Long lastUpdate = (Long) updateTimestamps.get(space); [color=red]1)[/color]
			if ( lastUpdate==null ) {
				//the last update timestamp was lost from the cache
				//(or there were no updates since startup!)
				//updateTimestamps.put( space, new Long( updateTimestamps.nextTimestamp() ) );
				//result = false; // safer
			}
			else {
			if ( lastUpdate.longValue() >= timestamp.longValue() ) return false; [color=red]2)[/color]
			}
		}
		return true;
	}

这个方法传入两个参数,一个是spaces,一个是需要比较的时间戳。对于第一个参数,从1)可以看出,它是取出需要跟参数2)
比较的时间戳的key,至于它具体的意思,我们可以看看另外一个与之相对应的method:
	public synchronized void preinvalidate(Serializable[] spaces) throws CacheException {
		//TODO: to handle concurrent writes correctly, this should return a Lock to the client
		Long ts = new Long( updateTimestamps.nextTimestamp() + updateTimestamps.getTimeout() ); [color=red]1;[/color]
		for ( int i=0; i<spaces.length; i++ ) updateTimestamps.put( spaces[i], ts );
		//TODO: return new Lock(ts);
	}

很明显,cache的key是由外部caller传入的参数,具体的含义我们可以由caller处找出,这个等下再分析。
值得关注的是ts,它等于当前下一个时间戳+updateTimestamps的失效时间。
回过头来看isUpToDate(Set spaces, Long timestamp),含义非常清楚,
在传入的keys中,如果其中一个key对应的最后修改时间戳大于参数2,则返回false。否则,返回upToDate.
这个upToDate是对于caller传入的参数2而言,而不是updateTimestamps而言,这个method的命名也是
符合围绕调用者定义的原则的。(一开始,我也是被这个method 命名迷惑)

好了,分析完UpdateTimestampsCache之后,我们重新回到SessionFactoryImpl上的1),
这里还要强调一点,SessionFactory维护着updateTimestampsCache对象,而所有的QueryCache实例中的
updateTimestampsCache实例变量都是引用自这个变量,这点很重要,说明SessionFactory管理着一个
对应于全部QueryCache的全局的时间戳表。这点可以从SessionFactoryImpl的2)得到验证。
2)那里终于出现了期盼已久的QueryCache对象,我们回到文章开始,再重新分析QueryCache对象,
它有两个实例变量
	private Cache queryCache; 1)
	private UpdateTimestampsCache updateTimestampsCache; 2)
	

queryCache是用来保存[queryKey, Result]的,而updateTimestampsCache则是用来
决定当前缓存的查询结果集和是否是脏数据的判断标准。为此,我们仔细分析这个get方法:
	public List get(QueryKey key, Type[] returnTypes, Set spaces, SessionImplementor session) throws HibernateException {
		if ( log.isDebugEnabled() ) log.debug("checking cached query results in region: " + regionName);
		List cacheable = (List) queryCache.get(key);
		if (cacheable==null) {
			log.debug("query results were not found in cache");
			return null;
		}
		List result = new ArrayList( cacheable.size()-1 );
		Long timestamp = (Long) cacheable.get(0);
		 [color=red]1)[/color]if ( !updateTimestampsCache.isUpToDate(spaces, timestamp) ) { 
			log.debug("cached query results were not up to date");
			return null;
		}
		log.debug("returning cached query results");
		for ( int i=1; i<cacheable.size(); i++ ) {
			if ( returnTypes.length==1 ) {
				result.add( returnTypes[0].assemble( (Serializable) cacheable.get(i), session, null ) );
			}
			else {
				result.add( TypeFactory.assemble( (Serializable[]) cacheable.get(i), returnTypes, session, null ) );
			}
		}
		return result;
	}
	

红色部分1)说明这点。
我们再从queryCache调用者角度进一步分析queryCache在hibernate 中的交互行为。
在sessionImpl中search“queryCache”发现:
	private void executeAll(List list) throws HibernateException {
		final boolean lockQueryCache = factory.isQueryCacheEnabled();
		int size = list.size();
		for ( int i=0; i<size; i++ ) {
			Executable executable = (Executable) list.get(i);
			executions.add(executable);
			 [color=red]1)[/color]if (lockQueryCache) factory.getUpdateTimestampsCache().preinvalidate( executable.getPropertySpaces() );
			executable.execute();
		}
		list.clear();
		if ( batcher!=null ) batcher.executeBatch();
	}
	

对于session中的操作,如果开启queryCache,则会更新SessionFactory的时间戳缓存。其中比较有意义的是:executable.getPropertySpaces()。
从UpdateTimestampsCache中可以看出,这个是作为保存最后更新时间戳的key,有必要看看他们到底是什么咚咚:
在ScheduledEntityAction中发现,它其实是调用ClassPersister 的getPropertySpaces()方法。

最后得出结论,通过Hibernate的sessionAPI调用的save,update,delete,都可以更新受影响的UpdateTimestampsCache。
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics