Mybatis 定义了org.apache.ibatis.cache.Cache接口作为Cache的SPI(Service Provider Interface),Mybatis所有的内部缓存都需要去实现这个接口。MyBatis定义了PerpetualCache实现类实现了Cache接口。事实上,为SqlSession对象创建的Executor内部维护的Cache类型实例对象就是PerpetualCache子类创建的。
- 一级缓存只涉及到PerpetualCache子类。
-
Cache的其它实现如下图:
Cache装饰器.png
如何确定Cache中key?
Cache最核心的实现就是HashMap,将查询使用的特征值作为key,将查询到的结果作为value,存储到HashMap中。
-
问题
如何确定Cache中key?也就是怎样来确定此次查询的特征值,也可以说是怎么样判断两次查询是完全相同的查询? -
分析
要确定两次查询是同一个查询,就需要满足以上四种特征:-
传入的statementId
对于MyBatis来说,要使用它,必须要有一个statementId,它代表着你会执行怎样的Sql。 -
查询中要求查询结果集中的结果范围
MyBatis自身的分页通过RowBounds来实现,它通过rowBounds.offset和rowBounds.limit来过滤查询出来的结果集。这种分页功能是查询结果的再过滤,并非是数据库的物理分页。 - 这次查询所产生的最终要传递给JDBC java.sql.Preparedstatement的sql语句字符串(boundSql.getSql() )
-
传递给java.sql.Statement要设置的参数值
第3和第4条的本质要求就是:调用JDBC时,传入的SQL语句要一模一样,传递给JDBC的参数值也要一模一样。
-
传入的statementId
-
结论
综上可得出,CacheKey的组合应如下:
statementId + rowBounds + 传递给JDBC的sql + 传递给JDBC的参数 -
CacheKey的创建
对于每次的查询请求,Executor会根据传递的参数信息以及动态生产的SQL语句,按上述条件根据一定的计算规则,创建对应的CacheKey对象。
1 目的
CacheKey作为key去缓存Cache中查找缓存结果;
如果查找缓存命中失败,可以此CacheKey为key,数据库查询的结果为value,组成键值对,存入Cache缓存中。-
代码
构建放在了Executor接口的实现类BaseExecutor中:
-
代码
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey();
//1.statementId
cacheKey.update(ms.getId());
//2. rowBounds.offset
cacheKey.update(rowBounds.getOffset());
//3. rowBounds.limit
cacheKey.update(rowBounds.getLimit());
//4. SQL语句
cacheKey.update(boundSql.getSql());
//5. 将每一个要传递给JDBC的参数值也更新到CacheKey中
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic
for (ParameterMapping parameterMapping : parameterMappings) {
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
cacheKey.update(value);
}
}
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
-
CacheKey的hashcode生成算法
Cache接口的实现,本质是使用HashMap<k,v>,CacheKey作为key值,HashMap通过key值的hashcode来组织和存储,也即是构建CacheKey实际上就是构造其hashCode的过程。
CacheKey的核心hashcode生成算法
public void update(Object object) {
if (object != null && object.getClass().isArray()) {
int length = Array.getLength(object);
for (int i = 0; i < length; i++) {
Object element = Array.get(object, i);
doUpdate(element);
}
} else {
doUpdate(object);
}
}
private void doUpdate(Object object) {
//1. 得到对象的hashcode;
int baseHashCode = object == null ? 1 : object.hashCode();
//对象计数递增
count++;
checksum += baseHashCode;
//2. 对象的hashcode 扩大count倍
baseHashCode *= count;
//3. hashCode * 拓展因子(默认37)+拓展扩大后的对象hashCode值
hashcode = multiplier * hashcode + baseHashCode;
updateList.add(object);
}
网友评论