一、一级缓存的介绍
一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数 据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的
二、一级缓存的执行过程
1、第一次发起查询用户信息,会先去找缓存中是否有id为1的用户信息,如果没有,从 数据
库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。
2、 如果中间sqlSession去执行commit操作(执行插入、更新、删除),则会清空SqlSession中的 一级 缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
(使用sqlSesion.clearCache()也可以手动刷新一级缓存)
3、 第二次发起查询用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息
三、一级缓存的结构
statementid:namespace+id
parmas:方法参数
boundSql:执行sql语句
rowBounds:分页参数
四、从sqlsession开始查找一级缓存
1.一级缓存的底层结构
查看sqlSession类下面的所有方法,发现只有方法名为 clearCache()的方法和缓存沾点关系 。
从此方法入手,查看其父类,得出以下关系:
不支持在 Docs 外粘贴 block
其中,在PerpetualCache中可以看出 ,最终使用了一个 cache.clear()方法 。而这里的cache是一个HashMap,说明一级缓存的底层为一个HashMap
2.一级缓存的创建
通过第一步的查找,跟踪清除缓存流程,得到的一级缓存的结构。但是一级缓存在哪里创建的呢?
我觉得是Executor,为什么这么认为?因为Executor是 执 行器,用来执行SQL请求,而且清除缓存的方法也在Executor中执行,所以很可能缓存的创建也很 有可 能在Executor中,看了一圈发现Executor中有一个createCacheKey方法,这个方法很像是创 建缓存的 方法,createCacheKey方法由BaseExecutor继承,代码如下
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (this.closed) {
throw new ExecutorException("Executor was closed.");
} else {
//MappedStatement 的 id
// id就是Sql语句的所在位置包名+类名+ SQL名称
cacheKey.update(ms.getId());
// offset 就是 0
cacheKey.update(rowBounds.getOffset());
// limit 就是 Integer.MAXVALUE
cacheKey.update(rowBounds.getLimit());
//具体的SQL语句
cacheKey.update(boundSql.getSql());
//后面是update 了 sql中带的参数
cacheKey.update(value);
......
if (this.configuration.getEnvironment() != null) {
//把key和value存进去
cacheKey.update(this.configuration.getEnvironment().getId());
}
return cacheKey;
}
}
这里需要注意一下最后一个值,configuration.getEnvironment().getId()这是什么,这其实就是 定义在 mybatis-config.xml中的标签,见如下。
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
update方法:
public void update(Object object) {
int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
++this.count;
this.checksum += (long)baseHashCode;
baseHashCode *= this.count;
this.hashcode = this.multiplier * this.hashcode + baseHashCode;
this.updateList.add(object);
}
创建缓存key会经过一系列的update方法,update方法由一个CacheKey这个对象来执行的,这个 update方法最终由updateList的list来把五个值存进去,对照上面的代码和下面的图示,你应该能 理解 这五个值都是什么了
3.一级缓存的使用
一级缓存在查询中使用,那我们看下BaseExecutor的query方法:
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
//此处调用上面说的createCacheKey创建缓存方法
CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql);
return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
......
try {
++this.queryStack;
//是否有缓存
list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
if (list != null) {
//若有缓存,从缓存中取值
this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//若无缓存,调用queryFromDatabase
list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
.....
}
queryFromDatabase:
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);
List list;
try {
//从数据库查询
list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
this.localCache.removeObject(key);
}
//查询结果存入缓存中
this.localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
this.localOutputParameterCache.putObject(key, parameter);
}
return list;
}