以前的文章我已經基本講解到了SqlSessionFactory、SqlSession、Excutor以及Mpper執行SQL過程,下面我來了解下myabtis的緩存,java
它的緩存分爲一級緩存和二級緩存,本文咱們主要分析下一級緩存。sql
先看一個例子,代碼仍是以前(第一篇)的的demo數據庫
public static void main(String[] args) throws Exception {
SqlSessionFactory sessionFactory = null;
String resource = "configuration.xml";
sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(resource));
SqlSession sqlSession = sessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
System.out.println(userMapper.findUserById(1));
System.out.println(userMapper.findUserById(1));
}
上面代碼咱們執行了兩次userMapper.findUserById(1),結果以下圖緩存
從執行結果看,DB的查詢只有1次,那麼第二次的查詢結果是在怎麼來的呢?微信
一:什麼是一級緩存
每當咱們使用MyBatis開啓一次和數據庫的會話,MyBatis會建立出一個SqlSession對象表示一次數據庫會話。 在對數據庫的一次會話中,咱們有可能會反覆地執行徹底相同的查詢語句,若是不採起一些措施的話,每一次查詢都會查詢一次數據庫,而咱們在極短的時間內作了徹底相同的查詢,那麼它們的結果極有可能徹底相同,因爲查詢一次數據庫的代價很大,這有可能形成很大的資源浪費。 session
爲了解決這一問題,減小資源的浪費,MyBatis會在表示會話的SqlSession對象中創建一個簡單的緩存,將每次查詢到的結果結果緩存起來,當下次查詢的時候,若是判斷先前有個徹底同樣的查詢,會直接從緩存中直接將結果取出,返回給用戶,不須要再進行一次數據庫查詢了。mybatis
基本的流程示意圖以下:架構
對於會話(Session)級別的數據緩存,咱們稱之爲一級數據緩存,簡稱一級緩存。app
二:如何執行緩存
咱們知道mybatis的SQL執行最後是交給了Executor執行器來完成的,咱們看下BaseExecutor類的源碼ide
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;//localCache 本地緩存
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); //若是緩存沒有就走DB
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);//清空現有緩存數據
}
localCache.putObject(key, list);//新的結果集存入緩存
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
經過上面的三個方法咱們基本已經看明白了緩存的使用,它的本地緩存使用的是PerpetualCache類,內部其實仍是一個HashMap,只是稍微作了封裝而已。
咱們再看下天的Key是如何生成的
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(Integer.valueOf(rowBounds.getOffset()));
cacheKey.update(Integer.valueOf(rowBounds.getLimit()));
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
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;
}
它是經過statementId,params,rowBounds,BoundSql來構建一個key值,根據這個key值去緩存Cache中取出對應的key值存儲的緩存結果。
咱們用一張圖來看清楚一級緩存的基本流程(本圖網上早來的,本身懶得畫了)
三:一級緩存生命週期
1. MyBatis在開啓一個數據庫會話時,會建立一個新的SqlSession對象,SqlSession對象中會有一個新的Executor對象,
Executor對象中持有一個新的PerpetualCache對象;當會話結束時,SqlSession對象及其內部的Executor對象還有PerpetualCache對象也一併釋放掉。
2. 若是SqlSession調用了close()方法,會釋放掉一級緩存PerpetualCache對象,一級緩存將不可用。
3. 若是SqlSession調用了clearCache(),會清空PerpetualCache對象中的數據,可是該對象仍可以使用。
4.SqlSession中執行了任何一個update操做(update()、delete()、insert()) ,都會清空PerpetualCache對象的數據,可是該對象能夠繼續使用。
本文分享自微信公衆號 - JAVA高級架構(gaojijiagou)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。