hello~各位讀者好,我是鴨血粉絲(你們能夠稱呼我爲「阿粉」)。今天,阿粉帶着你們來了解一下
mybatis
一級緩存的實現原理。mysql
0一、上期回顧
首先,咱們仍是回顧一下上篇文件的內容。畢竟離上次講 mybatis
仍是過去了好久,汗~。web
仍是先看下這個測試類,你們還有印象嗎:redis
public class MybatisTest {
@Test
public void testSelect() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
try {
FruitMapper mapper = session.getMapper(FruitMapper.class);
Fruit fruit = mapper.findById(1L);
System.out.println(fruit);
} finally {
session.close();
}
}
}
上篇源碼分析講了 SqlSession
構建的過程。此次,咱們來了解下 mybatis
一級緩存的實現原理。spring
0二、mybatis緩存
2.1 緩存的做用sql
mybatis
緩存的做用就是提高查詢的效率和減小數據庫的壓力。數據庫
2.2 mybatis的緩存類緩存
mybatis緩存相關的類都在cache包裏面,有個 Cache
的接口,默認實現是 PerpetualCache
類。固然,還有一些其餘緩存類,是經過裝飾器模式實現的。咱們來看下包結構:微信
而後看下這些緩存類的做用:session
-
PerpetualCache :基本緩存類,默認實現。 -
LruCache :LRU策略的緩存,做用是當緩存到達上限時候,刪除最近最少使用的緩存。 -
FifoCache :FIFO 策略的緩存,做用是當緩存到達上限時候,刪除最早入隊的緩存。 -
SoftCache :帶清理策略的緩存,做用是經過JVM 的軟引用來實現緩存,當JVM內存不足時,會自動清理掉這些緩存。 -
WeakCache :帶清理策略的緩存,做用是經過JVM 的弱引用來實現緩存,當JVM內存不足時,會自動清理掉這些緩存。 -
LoggingCache :帶日誌功能的緩存。 -
SynchronizedCache :同步緩存,基於synchronized 關鍵字實現,做用是解決併發問。 -
BlockingCache :阻塞緩存,經過在get/put 方式中加鎖,保證只有一個線程操做緩存,基於Java 重入鎖實現 -
SerializedCache :支持序列化的緩存,將對象序列化之後存到緩存中,取出時反序列化。 -
ScheduledCache :定時調度的緩存,在進行get/put/remove/getSize 等操做前,判斷緩存時間是否超過了設置的最長緩存時間(默認是一小時),若是是則清空緩存--即每隔一段時間清 空一次緩存。這個有點像 redis 設置的超時時間。 -
TransactionalCache :事務緩存。
2.3 一級緩存mybatis
一級緩存也叫本地緩存,MyBatis 的一級緩存是在會話(SqlSession)層面進行緩存的。MyBatis 的一級緩存是默認開啓的,不須要任何的配置。
上面說到緩存的默認實現對象是 PerpetualCache ,那麼這個對象是在哪裏維護的呢?MyBatis 的一級緩存是在會話共享,那麼咱們先看下 SqlSession 這個裏面有沒有維護。SqlSession 是接口,阿粉上一篇源碼講了建立 SqlSession 最後返回的是 DefaultSqlSession 對象。咱們看下這個類的成員變量:
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
private final boolean autoCommit;
private boolean dirty;
private List<Cursor<?>> cursorList;
。。。
}
首先,Configuration 對象是全局的,不可能放在這個裏面。後面3個也不像,咱們看下 Executor 類。
Executor 類也是一個接口,咱們看下它的抽象實現 BaseExecutor :
public abstract class BaseExecutor implements Executor {
private static final Log log = LogFactory.getLog(BaseExecutor.class);
protected Transaction transaction;
protected Executor wrapper;
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
protected PerpetualCache localCache;
protected PerpetualCache localOutputParameterCache;
protected Configuration configuration;
。。。
}
看到沒,緩存對象是在這個對象裏面維護的。Executor 這個類就是執行 sql 的,能夠理解爲 sql 執行器。
而後再來看下 PerpetualCache 類是怎麼實現緩存的。
public class PerpetualCache implements Cache {
private final String id;
private Map<Object, Object> cache = new HashMap<>();
。。。
}
很明顯,是用 HashMap 實現的。既然是 HashMap ,那麼用什麼做爲 key 呢?咱們看下 BaseExecutor 裏面有一個 createCacheKey() 的方法:
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(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
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) {
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
而後咱們來看下:
-
ms.getId() :ms 是解析 mapper.xml 建立的對象,每一個 select/update/delete/insert 標籤會建立一個 MappedStatement 對象。id 就是 mapper 的 namespace 加上 4種標籤的 id。 -
rowBounds.getOffset() :分頁參數。 -
rowBounds.getLimit() :分頁參數。 -
boundSql.getSql() :sql 語句。 -
value :這個是解析的 sql 傳入的參數。 -
configuration.getEnvironment().getId() :這個是配置的數據源 id ,spring 裏面,數據源不會在 mybatis裏面配置。
這就是緩存 key 的組成。
最後阿粉仍是用例子來驗證一下一級緩存是不是在 session 中共享的。判斷是否命中緩存,能夠根據是否打印 sql 來判斷,沒有緩存就會去數據庫查詢,全部會打印 sql,不然就是有緩存,不會打印sql。來看下例子:
@Test
public void testSelect() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
try {
FruitMapper mapper = session.getMapper(FruitMapper.class);
Fruit fruit = mapper.findById(1L);
System.out.println("第一次查詢:"+ fruit);
Fruit fruit1 = mapper.findById(1L);
System.out.println("第二次查詢:" + fruit1);
} finally {
session.close();
}
}
結果爲:
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@42f48531]
==> Preparing: select id,name from fruit where id =?
==> Parameters: 1(Long)
<== Columns: id, name
<== Row: 1, 蘋果
<== Total: 1
第一次查詢:Fruit(id=1, name=蘋果)
第二次查詢:Fruit(id=1, name=蘋果)
說明同一個 session 裏面,緩存是共享的。接下來咱們在不一樣的 session 中看下:
public class MybatisTest {
@Test
public void testSelect() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
SqlSession session2 = sqlSessionFactory.openSession();
try {
FruitMapper mapper = session.getMapper(FruitMapper.class);
FruitMapper mapper2 = session2.getMapper(FruitMapper.class);
Fruit fruit = mapper.findById(1L);
System.out.println("第一次查詢:"+ fruit);
Fruit fruit1 = mapper2.findById(1L);
System.out.println("第二次查詢:" + fruit1);
} finally {
session.close();
}
}
}
結果爲:
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@42f48531]
==> Preparing: select id,name from fruit where id =?
==> Parameters: 1(Long)
<== Columns: id, name
<== Row: 1, 蘋果
<== Total: 1
第一次查詢:Fruit(id=1, name=蘋果)
Opening JDBC Connection
Created connection 1358857082.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@50fe837a]
==> Preparing: select id,name from fruit where id =?
==> Parameters: 1(Long)
<== Columns: id, name
<== Row: 1, 蘋果
<== Total: 1
第二次查詢:Fruit(id=1, name=蘋果)
說明不一樣的 session ,緩存是不共享的。
0三、總結
今天的 mybatis 一級緩存到這裏就結束了。喜歡阿粉的同窗記得點個贊哦。咱們下次再見。
< END >
若是你們喜歡咱們的文章,歡迎你們轉發,點擊在看讓更多的人看到。也歡迎你們熱愛技術和學習的朋友加入的咱們的知識星球當中,咱們共同成長,進步。
本文分享自微信公衆號 - Java極客技術(Javageektech)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。