上次博客我們說了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的執行流程,源碼級。
感覺自己現在心中知道怎么去讀源碼,但是還是說不清楚,不能很好的表達出來,我再改進改進,可能還是看的不夠深吧。。。
最進弄了一個公眾號,小菜技術,歡迎大家的加入