文章目錄
- 1.一級緩存
- 1.1 一級緩存的概述
- 1.2 MyBatis中的一級緩存是怎樣組織的
- 1.3 一級緩存的生命周期
- 2.SqlSession 一級緩存的工作流程
- 3.Cache接口的設計以及CacheKey的定義
- 3.1 CacheKey的定義
- 3.2 CacheKey的建立
- 4.關閉一級緩存
- 4.1 什麼場景下必須需要關閉一級緩存
- 4.2 關閉一級緩存方法
1.一級緩存
1.1 一級緩存的概述
每當我們使用MyBatis開啟一次和資料庫的會話,MyBatis會建立出一個SqlSession對象表示一次資料庫會話。
MyBatis會在一次會話的表示----一個SqlSession對象中建立一個本地緩存(local cache),對于每一次查詢,都會嘗試根據查詢的條件去本地緩存中查找是否在緩存中,如果在緩存中,就直接從緩存中取出,然後傳回給使用者;否則,從資料庫讀取資料,将查詢結果存入緩存并傳回給使用者。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5iNycjM0YGO3ETZykjZkRGOyYzX4MTO0UTM1IzLcVDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
對于會話(Session)級别的資料緩存,我們稱之為一級資料緩存,簡稱一級緩存。
1.2 MyBatis中的一級緩存是怎樣組織的
SqlSession、Executor、Cache之間的關系:
Executor接口的實作類BaseExecutor中擁有一個Cache接口的實作類PerpetualCache,則對于BaseExecutor對象而言,它将使用PerpetualCache對象維護緩存。
SqlSession對象、Executor對象、Cache對象之間的關系:
PerpetualCache實作原理其實很簡單,其内部就是通過一個簡單的HashMap<k,v> 來實作的,沒有其他的任何限制。
public class PerpetualCache implements Cache {
private final String id;
private final Map<Object, Object> cache = new HashMap<>();
public PerpetualCache(String id) {
this.id = id;
}
@Override
public String getId() {
return id;
}
@Override
public int getSize() {
return cache.size();
}
@Override
public void putObject(Object key, Object value) {
cache.put(key, value);
}
@Override
public Object getObject(Object key) {
return cache.get(key);
}
@Override
public Object removeObject(Object key) {
return cache.remove(key);
}
@Override
public void clear() {
cache.clear();
}
@Override
public boolean equals(Object o) {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
if (this == o) {
return true;
}
if (!(o instanceof Cache)) {
return false;
}
Cache otherCache = (Cache) o;
return getId().equals(otherCache.getId());
}
@Override
public int hashCode() {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
return getId().hashCode();
}
}
1.3 一級緩存的生命周期
- MyBatis在開啟一個資料庫會話時,會 建立一個新的
對象,SqlSession
對象中會有一個新的SqlSession
對象,Executor
對象中持有一個新的Executor
對象;當會話結束時,PerpetualCache
對象及其内部的SqlSession
對象還有Executor
對象也一并釋放掉。PerpetualCache
- 如果
調用了SqlSession
方法,會釋放掉一級緩存close()
對象,一級緩存将不可用;PerpetualCache
- 如果SqlSession調用了clearCache(),會
中的資料,但是該對象仍可使用;清空PerpetualCache對象
- SqlSession中執行了任何一個update操作(update()、delete()、insert()) ,都會清空
對象的資料,但是該對象可以PerpetualCache
;繼續使用
2.SqlSession 一級緩存的工作流程
- 對于某個查詢,根據statementId,params,rowBounds來建構一個key值,根據這個key值去緩存Cache中取出對應的key值存儲的緩存結果;
- 判斷從Cache中根據特定的key值取的資料資料是否為空,即是否命中;
- 如果命中,則直接将緩存結果傳回;
- 如果沒命中: 去資料庫中查詢資料,得到查詢結果;将key和查詢到的結果分别作為key,value對存儲到Cache中;将查詢結果傳回;
3.Cache接口的設計以及CacheKey的定義
3.1 CacheKey的定義
Cache最核心的實作其實就是一個Map,将本次查詢使用的特征值作為key,将查詢結果作為value存儲到Map中。
MyBatis認為,對于兩次查詢,如果以下條件都完全一樣,那麼就認為它們是完全相同的兩次查詢
- 傳入的 statementId
- 查詢時要求的結果集中的結果範圍 (結果的範圍通過rowBounds.offset和rowBounds.limit表示);
- 這次查詢所産生的最終要傳遞給JDBC java.sql.Preparedstatement的Sql語句字元串(boundSql.getSql() )
- 傳遞給java.sql.Statement要設定的參數值
解釋:
- 傳入的statementId,對于MyBatis而言,你要使用它,必須需要一個statementId,它代表着你将執行什麼樣的Sql;
- MyBatis自身提供的分頁功能是通過RowBounds來實作的,它通過
和rowBounds.offset
來過濾查詢出來的結果集,這種分頁功能是基于查詢結果的再過濾,而不是進行資料庫的實體分頁;rowBounds.limit
- 第3個條件正是要求保證傳遞給JDBC的SQL語句完全一緻;第4條則是保證傳遞給JDBC的參數也完全一緻;
CacheKey:
statementId + rowBounds + 傳遞給JDBC的SQL + 傳遞給JDBC的參數值
3.2 CacheKey的建立
對于每次的查詢請求,Executor都會根據傳遞的參數資訊以及動态生成的SQL語句, 将上面的條件根據一定的計算規則,建立一個對應的CacheKey對象。
- 根據CacheKey作為key,去Cache緩存中查找緩存結果;
- 如果查找緩存命中失敗,則通過此CacheKey作為key,将從資料庫查詢到的結果作為value,組成key,value對存儲到Cache緩存中。
BaseExecutor#createCacheKey()
@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);
}
// 将每一個要傳遞給JDBC的參數值也更新到CacheKey中
cacheKey.update(value);
}
}
if (configuration.getEnvironment() != null) {
// 将每一個要傳遞給JDBC的參數值也更新到CacheKey中
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
CacheKey的hashcode生成算法:
public void update(Object object) {
int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
count++;
checksum += baseHashCode;
baseHashCode *= count;
hashcode = multiplier * hashcode + baseHashCode;
updateList.add(object);
}
4.關閉一級緩存
4.1 什麼場景下必須需要關閉一級緩存
4.2 關閉一級緩存方法
- xml:
<select flushCache="true"></select>
-
注解形式:
@Options(flushCache = Options.FlushCachePolicy.TRUE)
@Options(flushCache = Options.FlushCachePolicy.TRUE)
@Select("select * from ge_jdbc_datasource where id = #{id,jdbcType=BIGINT} and status = 1")
@ResultMap("resultMap")
JdbcDataSource find(Long id);
-
sql動态拼接傳入的随機數
比如sql傳參random()數值 或者 sql傳入目前時間毫秒數,切記一定要從方法形參傳過去而不要在sql中拼寫,否則無效
select id from ge_jdbc_datasource where id = 1 and STATUS = 1 AND NOW()=NOW()
- 執行SqlSession的commit(執行插入、更新、删除操作後)
- 執行SqlSession的close方法
- 執行SqlSession的clearCache方法
org.apache.ibatis.executor.BaseExecutor#update
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (this.closed) {
throw new ExecutorException("Executor was closed.");
} else {
//删除一級緩存
this.clearLocalCache();
return this.doUpdate(ms, parameter);
}
}
org.apache.ibatis.executor.BaseExecutor#commit
public void commit(boolean required) throws SQLException {
if (this.closed) {
throw new ExecutorException("Cannot commit, transaction is already closed");
} else {
//删除一級緩存
this.clearLocalCache();
this.flushStatements();
if (required) {
this.transaction.commit();
}
}
}
org.apache.ibatis.executor.BaseExecutor#rollback
public void rollback(boolean required) throws SQLException {
if (!this.closed) {
try {
//删除一級緩存
this.clearLocalCache();
this.flushStatements(true);
} finally {
if (required) {
this.transaction.rollback();
}
}
}
}