IBatis 的缓存机制之 Select Query缓存
可以按下面的代码在你的 SqlMap.xml 里配置,如下:
<
cacheModel
id
="users-cache"
imlementation
="LRU"
readOnly
="true"
serialize
="true"
>
< flushInterval hours ="24" />
< flushOnExecute statement ="users.update" />
< property name ="size" value ="1000" />
</ cacheModel >
< statement id ="findUsers" parameterClass ="int" cacheModel ="users-cache" >
select * from users where member_id = #value#
</ statement >
< flushInterval hours ="24" />
< flushOnExecute statement ="users.update" />
< property name ="size" value ="1000" />
</ cacheModel >
< statement id ="findUsers" parameterClass ="int" cacheModel ="users-cache" >
select * from users where member_id = #value#
</ statement >
我们来跟踪一下代码,看 IBatis 是如何实现缓存的,
还是由类:SqlMapParser 来解析这个配置的,代码如下:
private void addCacheModelNodelets() {
parser.addNodelet("/sqlMap/cacheModel", new Nodelet() {
public void process(Node node) throws Exception {
Properties attributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
String id = state.applyNamespace(attributes.getProperty("id"));
String type = attributes.getProperty("type");
String readOnlyAttr = attributes.getProperty("readOnly");
Boolean readOnly = readOnlyAttr == null || readOnlyAttr.length() <= 0 ? null : new Boolean("true".equals(readOnlyAttr));
String serializeAttr = attributes.getProperty("serialize");
Boolean serialize = serializeAttr == null || serializeAttr.length() <= 0 ? null : new Boolean("true".equals(serializeAttr));
type = state.getConfig().getTypeHandlerFactory().resolveAlias(type);
Class clazz = Resources.classForName(type);
if (readOnly == null) {
readOnly = Boolean.TRUE;
}
if (serialize == null) {
serialize = Boolean.FALSE;
}
CacheModelConfig cacheConfig = state.getConfig().newCacheModelConfig(id, (CacheController) Resources.instantiate(clazz), readOnly.booleanValue(), serialize.booleanValue());
state.setCacheConfig(cacheConfig);
}
});
parser.addNodelet("/sqlMap/cacheModel/end()", new Nodelet() {
public void process(Node node) throws Exception {
state.getCacheConfig().setControllerProperties(state.getCacheProps());
}
});
parser.addNodelet("/sqlMap/cacheModel/property", new Nodelet() {
public void process(Node node) throws Exception {
state.getConfig().getErrorContext().setMoreInfo("Check the cache model properties.");
Properties attributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
String name = attributes.getProperty("name");
String value = NodeletUtils.parsePropertyTokens(attributes.getProperty("value"), state.getGlobalProps());
state.getCacheProps().setProperty(name, value);
}
});
parser.addNodelet("/sqlMap/cacheModel/flushOnExecute", new Nodelet() {
public void process(Node node) throws Exception {
Properties childAttributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
String statement = childAttributes.getProperty("statement");
state.getCacheConfig().addFlushTriggerStatement(statement);
}
});
parser.addNodelet("/sqlMap/cacheModel/flushInterval", new Nodelet() {
public void process(Node node) throws Exception {
Properties childAttributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
try {
int milliseconds = childAttributes.getProperty("milliseconds") == null ? 0 : Integer.parseInt(childAttributes.getProperty("milliseconds"));
int seconds = childAttributes.getProperty("seconds") == null ? 0 : Integer.parseInt(childAttributes.getProperty("seconds"));
int minutes = childAttributes.getProperty("minutes") == null ? 0 : Integer.parseInt(childAttributes.getProperty("minutes"));
int hours = childAttributes.getProperty("hours") == null ? 0 : Integer.parseInt(childAttributes.getProperty("hours"));
state.getCacheConfig().setFlushInterval(hours, minutes, seconds, milliseconds);
} catch (NumberFormatException e) {
throw new RuntimeException("Error building cache in '" + "resourceNAME" + "'. Flush interval milliseconds must be a valid long integer value. Cause: " + e, e);
}
}
});
}
带缓存的SQL具体的执行是由类:CachingStatement 来执行,如要执行executeQueryForList动作,其执行的代码如下:
public List executeQueryForList(StatementScope statementScope, Transaction trans, Object parameterObject, int skipResults, int maxResults)
throws SQLException {
CacheKey cacheKey = getCacheKey(statementScope, parameterObject);
cacheKey.update("executeQueryForList");
cacheKey.update(skipResults);
cacheKey.update(maxResults);
Object listAsObject = cacheModel.getObject(cacheKey);
List list;
if(listAsObject == CacheModel.NULL_OBJECT){
// The cached object was null
list = null;
}else if (listAsObject == null) {
list = statement.executeQueryForList(statementScope, trans, parameterObject, skipResults, maxResults);
cacheModel.putObject(cacheKey, list);
}else{
list = (List) listAsObject;
}
return list;
}
上面的代码很明显,如果在缓存中存在此数据就直接返回此数据,如果没有找到则执行具体的查询操作,并将查询后的结果缓存到缓存中去,再次使用时就直接返回缓存里的数据了。
这里面的 CacheKey 很有意思,Ibatis 是按照 SQL 加参数并加参数个数进行 Hash 组成一个 Key。因此我们的查询中 SQL的参数变化的频率太多时,使用缓存的效果将十分不明显,
甚至可能取得相反的效果,每次查询缓存也是需要时间的。至于 IBatis 何时更新缓存里的数据,请参看我之前写的这遍文章 IBatis 的缓存机制( http://lifei114.iteye.com/admin/blogs/581795)
具体项目中如何使用缓存我认为具体问题具体分析,不要盲目相信科学神话,“大家都在用的东西不一定就适合自己用”,以上供大家参考,欢迎大家一起讨论,我的 QQ: 535043378 ,最近加的人太多请注明 "JavaEye"。
r