用了這么久的Mybatis,結(jié)果面試官問的問題,我竟然還猶豫了
本文轉(zhuǎn)載自微信公眾號「Java極客技術(shù)」,作者鴨血粉絲。轉(zhuǎn)載本文請聯(lián)系Java極客技術(shù)公眾號。
前段時間阿粉的一個朋友和阿粉吃飯,在吃飯的時候和阿粉瘋狂的吐槽面試官,說面試官問的問題都是些什么問題呀,我一個干了三四年的開發(fā),也不說問點靠譜的,阿粉很好奇,問題問完基礎(chǔ)的,一般不都是根據(jù)你自己的簡歷進行提問么?而接下來他說的出來的問題,阿粉表示,阿粉需要繼續(xù)學(xué)習(xí)了。
Mybatis是什么?
說到這個,讀者大人們肯定心想,阿粉是在開玩笑么?你一個 Java 程序員,你不知道Mybatis是啥么?不就是個持久層的框架么,這東西有啥好說的呢?但是阿粉還是要給大家說。
Mybatis是一個半自動 ORM(對象關(guān)系映射)框架,它內(nèi)部封裝了JDBC,加載驅(qū)動、創(chuàng)建連接、創(chuàng)建 statement 等繁雜的過程,我們開發(fā)的時候只需要關(guān)注如何編寫 SQL 語句,而不用關(guān)心其他的。
為什么說 Mybatis 是一個半自動 ORM 的框架呢?
ORM,是Object和Relation之間的映射,而Mybatis 在查詢關(guān)聯(lián)對象或關(guān)聯(lián)集合對象時,需要手動編寫 sql 來完成,所以,稱之為半自動 ORM 框架,而Hibernate 屬于全自動 ORM 映射工具,使用 Hibernate 查詢關(guān)聯(lián)對象或者關(guān)聯(lián)集合對象時,可以根據(jù)對象關(guān)系模型直接獲取,所以它是全自動的。
這也是為什么有些面試官在面試初級程序員的時候,很喜歡說,你覺得 Mybatis , 和 Hibernate 都有什么優(yōu)缺點,為啥你們選擇使用的 Mybatis 而不選擇使用 Hibernate 呢?
我們都說了 Mybatis是什么了,接下來肯定需要說說面試官都問了什么問題,能讓阿粉的朋友變得非常猶豫。
Mybatis的一級、二級緩存是什么你了解么?
Mybatis 的一級緩存
我們先說 Mybatis 的一級緩存,因為這是如果不手動配置,他是自己默認開啟的一級緩存,一級緩存只是相對于同一個 SqlSession 而言,參數(shù)和SQL完全一樣的情況下,我們使用同一個SqlSession對象調(diào)用一個Mapper方法,往往只執(zhí)行一次SQL,因為使用SelSession第一次查詢后,MyBatis會將其放在緩存中,以后再查詢的時候,如果沒有聲明需要刷新,并且緩存沒有超時的情況下,SqlSession都會取出當前緩存的數(shù)據(jù),而不會再次發(fā)送SQL到數(shù)據(jù)庫。
當我們面試的時候,說完這個,一般情況下,面試官一定會追問下去,畢竟技術(shù)就是要問到你的知識盲區(qū)才會停止。
那我們就來畫個圖表示一下一級緩存:
那面試官肯定會說,直接從數(shù)據(jù)庫查不就行了,為啥要一級緩存呢?
當我們使用MyBatis開啟一次和數(shù)據(jù)庫的會話時, MyBatis 會創(chuàng)建出一個 SqlSession 對象表示一次與數(shù)據(jù)庫之間的信息傳遞,在我們執(zhí)行 SQL 語句的過程中,們可能會反復(fù)執(zhí)行完全相同的查詢語句,如果不采取一些措施,我們每一次查詢都會查詢一次數(shù)據(jù)庫,而如果在極短的時間內(nèi)做了很多次相同的查詢操作,那么這些查詢返回的結(jié)果很可能相同。
也就是說,如果我們在短時間內(nèi),頻繁的去執(zhí)行一條 SQL ,查詢返回的結(jié)果本來應(yīng)該是改變了,但是我們查詢出來的時候,會出現(xiàn)結(jié)果一致的情況,正是為了解決這種問題,也為了減輕數(shù)據(jù)庫的開銷,所以 Mybatis 默認開啟了一級緩存。
Mybatis 的二級緩存
Mybatis 的二級緩存一般如果你不對他進行設(shè)置,他是不會開啟的,而二級緩存是什么呢?Mybatis 中的二級緩存實際上就是 mapper 級別的緩存,而這時候肯定會有人說,那么不同之間的 Mapper 是同一個緩存么?
答案是否定的,他不是一個,Mapper 級別的緩存實際上就是相同的 Mapper 使用的是一個二級緩存,但是在二級緩存中,又有多個不同的 SqlSession ,而不同的 Mapper 之間的二級緩存也就是互相不會影響的。
就類似下面的圖:
這二級緩存是不是就看起來有點意思了?
那怎么能夠開啟二級緩存呢?
1.MyBatis 配置文件
- <settings>
- <setting name = "cacheEnabled" value = "true" />
- </settings>
2.MyBatis 要求返回的 POJO 必須是可序列化的
3.Mapper 的 xml 配置文件中加入 標簽
既然我們想要了解這個二級緩存,那么必然,我們還得知道它里面的配置都有哪些含義。
我們先從標簽看起,然后從源碼里面看都有哪些配置信息提供給我們使用:
blocking : 直譯就是調(diào)度,而在 Mybatis 中,如果緩存中找不到對應(yīng)的 key ,是否會一直 blocking ,直到有對應(yīng)的數(shù)據(jù)進入緩存。
eviction : 緩存回收策略
而緩存回收策略,在源碼中是有直接體現(xiàn)的,那么他們分別都對應(yīng)了什么形式呢?
- typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
- typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
- typeAliasRegistry.registerAlias("LRU", LruCache.class);
- typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
- typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
- PERPETUAL : 選擇 PERPETUAL 來命名緩存,暗示這是一個最底層的緩存,數(shù)據(jù)一旦存儲進來,永不清除.好像這種緩存不怎么受待見。
- FIFO : 先進先出:按對象進入緩存的順序來移除它們
- LRU : 最近最少使用的:移除最長時間不被使用的對象。
- SOFT : 軟引用:移除基于垃圾回收器狀態(tài)和軟引用規(guī)則的對象。
- WEAK : 弱引用:更積極地移除基于垃圾收集器狀態(tài)和弱引用規(guī)則的對象。
大家雖然看著 PERPETUAL 排在了第一位,但是它可不是默認的,在 Mybatis 的緩存策略里面,默認的是 LRU 。
PERPETUAL :
源代碼如下:
- public class PerpetualCache implements Cache {
- private final String id;
- private Map<Object, Object> cache = new HashMap<>();
- public PerpetualCache(String id) {
- this.id = id;
- }
恩?看著是不是有點眼熟,它怎么就只是包裝了 HashMap ? 你還別奇怪,他還真的就是使用的 HashMap ,不得不說,雖然人家是使用的 HashMap ,但是那可是比咱們寫的高端多了。
既然使用 HashMap ,那么必然就會有Key,那么他們的Key是怎么設(shè)計的?
CacheKey:
- public class CacheKey implements Cloneable, Serializable {
- private static final long serialVersionUID = 1146682552656046210L;
- public static final CacheKey NULL_CACHE_KEY = new NullCacheKey();
- private static final int DEFAULT_MULTIPLYER = 37;
- private static final int DEFAULT_HASHCODE = 17;
- private final int multiplier;
- private int hashcode; //用于表示CacheKey的哈希碼
- private long checksum; //總和校驗,當出現(xiàn)復(fù)合key的時候,分布計算每個key的哈希碼,然后求總和
- private int count;//當出現(xiàn)復(fù)合key的時候,計算key的總個數(shù)
- // 8/21/2017 - Sonarlint flags this as needing to be marked transient. While true if content is not serializable, this is not always true and thus should not be marked transient.
- private List<Object> updateList;//當出現(xiàn)復(fù)合key的時候,保存每個key
確實牛逼,至于內(nèi)部如何初始化,如何進行操作,大家有興趣的可以去閱讀一下源碼,導(dǎo)入個源碼包,打開自己看一下。
FIFO: 先進先出緩沖淘汰策略
- public class FifoCache implements Cache {
- private final Cache delegate; //被裝飾的Cache對象
- private final Deque<Object> keyList;//用于記錄key 進入緩存的先后順序
- private int size;//記錄了緩存頁的上限,超過該值需要清理緩存(FIFO)
- public FifoCache(Cache delegate) {
- this.delegate = delegate;
- this.keyList = new LinkedList<>();
- this.size = 1024;
- }
在 FIFO 淘汰策略中使用了 Java 中的 Deque,而 Deque 一種常用的數(shù)據(jù)結(jié)構(gòu),可以將隊列看做是一種特殊的線性表,該結(jié)構(gòu)遵循的先進先出原則。Java中,LinkedList實現(xiàn)了Queue接口,因為LinkedList進行插入、刪除操作效率較高。
當你看完這個源碼的時候,是不是就感覺源碼其實也沒有那么難看懂,里面都是我們已經(jīng)掌握好的知識,只不過中間做了一些操作,進行了一些封裝。
LRU : 最近最少使用的緩存策略
而 LUR 算法,阿粉之前都說過,如果對這個算法感興趣的話,文章地址給大家送上,經(jīng)典的 LRU 算法,你真的了解嗎?
而我們需要看的源碼則是在 Mybatis 中的源碼,
- public class LruCache implements Cache {
- private final Cache delegate;
- private Map<Object, Object> keyMap;
- private Object eldestKey;//記錄最少被使用的緩存項key
- public LruCache(Cache delegate) {
- this.delegate = delegate;
- setSize(1024);//重新設(shè)置緩存的大小,會重置KeyMap 字段 如果到達上限 則更新eldestKey
- }
- public void putObject(Object key, Object value) {
- delegate.putObject(key, value);
- // 刪除最近未使用的key
- cycleKeyList(key);
- }
SOFT: 基于垃圾回收器狀態(tài)和軟引用規(guī)則的對象
在看到基于垃圾回收器的時候,阿粉就已經(jīng)開始興奮了,竟然有GC的事情,那還不趕緊看看,這如此高大上(裝杯)的事情,來瞅瞅吧!
- public class SoftCache implements Cache {
- //在SoftCache 中,最近使用的一部分緩存項不會被GC回收,這就是通過將其value添加到
- private final Deque<Object> hardLinksToAvoidGarbageCollection;
- //引用隊列,用于記錄GC回收的緩存項所對應(yīng)的SoftEntry對象
- private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
- //底層被修飾的Cache 對象
- private final Cache delegate;
- //連接的個數(shù),默認是256
- private int numberOfHardLinks;
- public SoftCache(Cache delegate) {
- this.delegate = delegate;
- this.numberOfHardLinks = 256;
- this.hardLinksToAvoidGarbageCollection = new LinkedList<>();
- this.queueOfGarbageCollectedEntries = new ReferenceQueue<>();
- }
- public void putObject(Object key, Object value) {
- // 清除被GC回收的緩存項
- removeGarbageCollectedItems();
- // 向緩存中添加緩存項
- delegate.putObject(key, new SoftEntry(key, value, queueOfGarbageCollectedEntries));
- }
- public Object getObject(Object key) {
- Object result = null;
- // 查找對應(yīng)的緩存項
- @SuppressWarnings("unchecked") // assumed delegate cache is totally managed by this cache
- SoftReference<Object> softReference = (SoftReference<Object>) delegate.getObject(key);
- if (softReference != null) {
- result = softReference.get();
- // 已經(jīng)被GC 回收
- if (result == null) {
- // 從緩存中清除對應(yīng)的緩存項
- delegate.removeObject(key);
- } else {
- // See #586 (and #335) modifications need more than a read lock
- synchronized (hardLinksToAvoidGarbageCollection) {
- hardLinksToAvoidGarbageCollection.addFirst(result);
- if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
- hardLinksToAvoidGarbageCollection.removeLast();
- }
- }
- }
- }
- return result;
- }
- public void clear() {
- synchronized (hardLinksToAvoidGarbageCollection) {
- // 清理強引用集合
- hardLinksToAvoidGarbageCollection.clear();
- }
- // 清理被GC回收的緩存項
- removeGarbageCollectedItems();
- delegate.clear();
- }
- //其中指向key的引用是強引用,而指向value的引用是弱引用
- private static class SoftEntry extends SoftReference<Object> {
- private final Object key;
- SoftEntry(Object key, Object value, ReferenceQueue<Object> garbageCollectionQueue) {
- super(value, garbageCollectionQueue);
- this.key = key;
- }
- }
WEAK : 基于垃圾收集器狀態(tài)和弱引用規(guī)則的對象
- public class WeakCache implements Cache {
- private final Deque<Object> hardLinksToAvoidGarbageCollection;
- private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
- private final Cache delegate;
- private int numberOfHardLinks;
- public WeakCache(Cache delegate) {
- this.delegate = delegate;
- this.numberOfHardLinks = 256;
- this.hardLinksToAvoidGarbageCollection = new LinkedList<>();
- this.queueOfGarbageCollectedEntries = new ReferenceQueue<>();
- }
WeakCache在實現(xiàn)上與SoftCache幾乎相同,只是把引用對象由SoftReference軟引用換成了WeakReference弱引用。
在這里阿粉也就不再多說了,關(guān)于 Mybatis 的二級緩存,你了解了么?下次遇到面試官問這個的時候,你應(yīng)該知道怎么成功(裝杯)不被打了吧。