java架構之路-(mybatis源碼)mybatis的一二級緩存問題


  上次博客我們說了mybatis的基本使用,我們還捎帶提到一下Mapper.xml中的select標簽的useCache屬性,這個就是設置是否存入二級緩存的。

回到我們正題,經常使用mybatis的小伙伴都知道,我們的mybatis是有兩級緩存的,一級緩存默認開啟,我們先來一下一級緩存吧,超級簡單。
一級緩存:

我們還拿上次的源碼來說

package mybatis;

import mybatis.bean.StudentBean;
import mybatis.dao.StudentMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;

public class Test1 {

    public SqlSession session;
    public SqlSessionFactory sqlSessionFactory;

    @Before
    public void init() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        session =  sqlSessionFactory.openSession();
    }

    @Test
    public void studentTest(){
        StudentMapper mapper = session.getMapper(StudentMapper.class);
        StudentBean result = mapper.selectUser(1);//這句執行了sql,也就是說,這句給一級緩存塞了值
        StudentBean result2 = mapper.selectUser(1);//這句執行了sql,也就是說,這句給一級緩存塞了值

        System.out.println(result==result2);
    }
}

我們可以看到打印結果為true,說明了命中了我們的一級緩存。

一級緩存的限制比較多,需要在同一個session,同一個會話,同一個方法(statement),內執行完全相同的sql,才能保證緩存的成功。

我們打開Mybatis里的BaseExecutor類我們找到152行代碼。

list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;

這個就是我們的一級緩存localCache。清空方法是在BaseExcutor的116行clearLocalCache,來清空我們的一級緩存的,所以說執行update以后一級緩存會被清空,后面有機會我會告訴大家我是怎么找到的,只要記住一級緩存默認開啟,是sqlSession級別的,幾乎是沒有生命的。然后記住什么情況下可以用,什么情況下不可以用,初級面試應該可以應付。

二級緩存:

二級緩存需要手動設置,只要在我們的配置文件內加入Cache標簽就可以了。或者加入@Cache注解也是ok的,二級緩存是在session關閉時才寫入的。為什么這樣設計呢?我們來假想一下,我們開啟session,做了一個insert寫入,這時還沒有提交,然后我們進行了查詢,如果這時寫入緩存,然后我們將insert進行回滾,那么我們的緩存就多了我們剛才寫入的數據,這樣的設計是顯然不合理的,我們先來看一下二級緩存是怎么設置的。
誰說查詢時候先查二級緩存,二級緩存沒有再查一級緩存的,一律打死,一級緩存作用在session會話范圍,你二級緩存的存入條件是session關閉,session都關閉了,還有毛線一級緩存了....

還是上次的代碼:我們來回顧一下。

package mybatis;

import mybatis.bean.StudentBean;
import mybatis.dao.StudentMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;

public class Test1 {

    public SqlSession session;
    public SqlSessionFactory sqlSessionFactory;

    @Before
    public void init() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        session =  sqlSessionFactory.openSession();
    }

    @Test
    public void studentTest(){
        StudentMapper mapper = session.getMapper(StudentMapper.class);
        StudentBean result = mapper.selectUser(1);
        session.close();

        session = sqlSessionFactory.openSession();
        StudentMapper mapper2 = session.getMapper(StudentMapper.class);
        StudentBean result2 = mapper2.selectUser(1);
        System.out.println(result == result2);

    }

}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mybatis.dao.StudentMapper">
    <cache></cache>
    <select id="selectUser" resultType="mybatis.bean.StudentBean">
        select * from student t where t.id = #{id}
    </select>
</mapper>

我們只需要加入cache標簽即可以使用我們的二級緩存。select標簽內有一個useCache屬性設置成false就是說,這個sql不寫入我們的緩存。需要注意的是要給予我們的實體Bean序列化,正因為序列化,我們的輸入結果是false,說明並不是一個對象的。后面我會解釋為什么需要做一個序列化,可以帶着問題繼續閱讀。

 注解方式這樣寫就ok了。

package mybatis.dao;

import mybatis.bean.StudentBean;
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.annotations.Select;


@CacheNamespace
public interface StudentMapper {

    @Select("select * from student t where t.id = #{id}")
    StudentBean selectUser(int id);
}

二級緩存適用范圍:

1,必須是session提交以后,二級緩存才寫入。

2,必須是同一個命名空間之下。

3,必須是相同的sql和參數。

4,如果是readWrite=true,實體類必須序列化

@CacheNamespace(readWrite = false)

這也就是我們說的為什么需要實例化,其實也可以不序列化的。但是我們要是改了其中一個數據,另外一個拿到的數據一定是修改后的,沒有特殊需求最好是做一個序列化,不要寫readWrite=false的設置,不寫readWrite=false會提高一點點性能,但是自我覺得沒必要冒那種風險。拿着這段代碼自己測試一下,不帶序列化的深拷貝對象會造成的結果。

package mybatis;

import mybatis.bean.StudentBean;
import mybatis.dao.StudentMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;

public class Test1 {

    public SqlSession session;
    public SqlSessionFactory sqlSessionFactory;

    @Before
    public void init() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        session =  sqlSessionFactory.openSession();
    }

    @Test
    public void studentTest(){
        StudentMapper mapper = session.getMapper(StudentMapper.class);
        StudentBean result = mapper.selectUser(1);
        System.out.println(result);
        result.setId(2222);
        session.commit();

        session = sqlSessionFactory.openSession();
        StudentMapper mapper2 = session.getMapper(StudentMapper.class);
        StudentBean result2 = mapper2.selectUser(1);
        System.out.println(result2);

        System.out.println(result == result2);
    }
}

5,必須是相同的statement相同的方法。

內部還可以加很多屬性的。

@CacheNamespace(
implementation = PerpetualCache.class, // 緩存實現 Cache接口 實現類
eviction = LruCache.class,// 緩存算法
flushInterval = 60000, // 刷新間隔時間 毫秒
size = 1024, // 最大緩存引用對象
readWrite = true, // 是否可寫
blocking = false // 是否阻塞,防止緩存擊穿的。
)

我們來簡單的深入一下二級緩存的源碼,我們在Mybatis的包里會看到這樣一個文件,一個叫Cache的文件,也就是我們的緩存文件。

 

 而且我們發現很多叫***Cahe的類都實現了他

 

 TransactionalCache注釋里明顯的寫到The 2nd level cache transactional buffer.二級緩存事務緩沖區。那么我們把斷點打在他的get和put方法上,(可能是一個錯誤的示范,我會一步步告訴你們錯了怎么改)

斷點進到了getObject方法,我們點擊開右邊的參數欄,點擊this,我們會看到我們的delegate參數,寫着什么什么cache,再次點擊還會發現什么什么Cache,直到不能向下點擊為止

 

 我們發現貌似實際存儲的貌似是PerpetualCache,我們發現我們的錯誤了,重新來過,清楚斷點,打開我們的PerpetualCache類,斷點重新打在PerpetualCache類的get和put方法下。我們左側的方法區,我們看到是這樣的

 

 從Perpetualcahe一直查到TransactionalCache,我們來張圖解釋一下。

 

 大致就是這樣的,逐層去尋找的。這里就是一個裝飾者模式。

  我們還可以將斷點打在CachingExecutor方法的query方法下來觀察我們的二級緩存。這個方法在很早就先幫我們把Cache獲取好了,且直接獲取到SynchronizedCache層了。有興趣的小伙伴可以自行測試一下,這里我就不再多說了,下次博客我們來具體深入的來看看Mybatis的執行流程,源碼級。

感覺自己現在心中知道怎么去讀源碼,但是還是說不清楚,不能很好的表達出來,我再改進改進,可能還是看的不夠深吧。。。

最進弄了一個公眾號,小菜技術,歡迎大家的加入

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM