mybatis系列之mybatis一級緩存

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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索