日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

深入了解MyBatis二级缓存

發布時間:2025/4/5 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深入了解MyBatis二级缓存 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

深入了解MyBatis二級緩存

一、創建Cache的完整過程

我們從SqlSessionFactoryBuilder解析mybatis-config.xml配置文件開始:

Reader reader = Resources.getResourceAsReader("mybatis-config.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

然后是:

XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); return build(parser.parse());

看parser.parse()方法:

parseConfiguration(parser.evalNode("/configuration"));

看處理Mapper.xml文件的位置:

mapperElement(root.evalNode("mappers"));

看處理Mapper.xml的XMLMapperBuilder:

XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse();

繼續看parse方法:

configurationElement(parser.evalNode("/mapper"));

到這里:

String namespace = context.getStringAttribute("namespace"); if (namespace.equals("")) {throw new BuilderException("Mapper's namespace cannot be empty"); } builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode("cache-ref")); cacheElement(context.evalNode("cache"));

從這里看到namespace就是xml中<mapper>元素的屬性。然后下面是先后處理的cache-ref和cache,后面的cache會覆蓋前面的cache-ref,但是如果一開始cache-ref沒有找到引用的cache,他就不會被覆蓋,會一直到最后處理完成為止,最后如果存在cache,反而會被cache-ref覆蓋。這里是不是看著有點暈、有點亂?所以千萬別同時配置這兩個,實際上也很少有人會這么做。

看看MyBatis如何處理<cache/>:

private void cacheElement(XNode context) throws Exception {if (context != null) {String type = context.getStringAttribute("type", "PERPETUAL");Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);String eviction = context.getStringAttribute("eviction", "LRU");Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);Long flushInterval = context.getLongAttribute("flushInterval");Integer size = context.getIntAttribute("size");boolean readWrite = !context.getBooleanAttribute("readOnly", false);boolean blocking = context.getBooleanAttribute("blocking", false);Properties props = context.getChildrenAsProperties();builderAssistant.useNewCache(typeClass, evictionClass,flushInterval, size, readWrite, blocking, props);} }

從源碼可以看到MyBatis讀取了那些屬性,而且很容易可以到這些屬性的默認值。

創建Java的cache對象方法為builderAssistant.useNewCache,我們看看這段代碼:

public Cache useNewCache(Class<? extends Cache> typeClass,Class<? extends Cache> evictionClass,Long flushInterval,Integer size,boolean readWrite,boolean blocking,Properties props) {typeClass = valueOrDefault(typeClass, PerpetualCache.class);evictionClass = valueOrDefault(evictionClass, LruCache.class);Cache cache = new CacheBuilder(currentNamespace).implementation(typeClass).addDecorator(evictionClass).clearInterval(flushInterval).size(size).readWrite(readWrite).blocking(blocking).properties(props).build();configuration.addCache(cache);currentCache = cache;return cache; }

從調用該方法的地方,我們可以看到并沒有使用返回值cache,在后面的過程中創建MappedStatement的時候使用了currentCache。

二、使用Cache過程

在系統中,使用Cache的地方在CachingExecutor中:

@Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {Cache cache = ms.getCache();

獲取cache后,先判斷是否有二級緩存。?
只有通過<cache/>,<cache-ref/>或@CacheNamespace,@CacheNamespaceRef標記使用緩存的Mapper.xml或Mapper接口(同一個namespace,不能同時使用)才會有二級緩存。

if (cache != null) {

如果cache存在,那么會根據sql配置(<insert>,<select>,<update>,<delete>的flushCache屬性來確定是否清空緩存。

flushCacheIfRequired(ms);

然后根據xml配置的屬性useCache來判斷是否使用緩存(resultHandler一般使用的默認值,很少會null)。

if (ms.isUseCache() && resultHandler == null) {

確保方法沒有Out類型的參數,mybatis不支持存儲過程的緩存,所以如果是存儲過程,這里就會報錯。

ensureNoOutParams(ms, parameterObject, boundSql);

沒有問題后,就會從cache中根據key來取值:

@SuppressWarnings("unchecked")List<E> list = (List<E>) tcm.getObject(cache, key);

如果沒有緩存,就會執行查詢,并且將查詢結果放到緩存中。

if (list == null) {list = delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);tcm.putObject(cache, key, list); // issue #578 and #116}

返回結果

return list;}}

沒有緩存時,直接執行查詢

return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }

在上面的代碼中tcm.putObject(cache, key, list);這句代碼是緩存了結果。但是實際上直到sqlsession關閉,MyBatis才以序列化的形式保存到了一個Map(默認的緩存配置)中。



三、Cache使用時的注意事項

1. 只能在【只有單表操作】的表上使用緩存

不只是要保證這個表在整個系統中只有單表操作,而且和該表有關的全部操作必須全部在一個namespace下。

2. 在可以保證查詢遠遠大于insert,update,delete操作的情況下使用緩存

這一點不需要多說,所有人都應該清楚。記住,這一點需要保證在1的前提下才可以!?


四、避免使用二級緩存

可能會有很多人不理解這里,二級緩存帶來的好處遠遠比不上他所隱藏的危害。

  • 緩存是以namespace為單位的,不同namespace下的操作互不影響。

  • insert,update,delete操作會清空所在namespace下的全部緩存。

  • 通常使用MyBatis Generator生成的代碼中,都是各個表獨立的,每個表都有自己的namespace。

  • 為什么避免使用二級緩存

    在符合【Cache使用時的注意事項】的要求時,并沒有什么危害。

    其他情況就會有很多危害了。

    針對一個表的某些操作不在他獨立的namespace下進行。

    例如在UserMapper.xml中有大多數針對user表的操作。但是在一個XXXMapper.xml中,還有針對user單表的操作。

    這會導致user在兩個命名空間下的數據不一致。如果在UserMapper.xml中做了刷新緩存的操作,在XXXMapper.xml中緩存仍然有效,如果有針對user的單表查詢,使用緩存的結果可能會不正確。

    更危險的情況是在XXXMapper.xml做了insert,update,delete操作時,會導致UserMapper.xml中的各種操作充滿未知和風險。

    有關這樣單表的操作可能不常見。但是你也許想到了一種常見的情況。

    多表操作一定不能使用緩存

    為什么不能?

    首先不管多表操作寫到那個namespace下,都會存在某個表不在這個namespace下的情況。

    例如兩個表:role和user_role,如果我想查詢出某個用戶的全部角色role,就一定會涉及到多表的操作。

    <code class="language-xml hljs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">select</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">id</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"selectUserRoles"</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">resultType</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"UserRoleVO"</span>></span>select * from user_role a,role b where a.roleid = b.roleid and a.userid = #{userid} <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"></<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">select</span>></span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul>

    像上面這個查詢,你會寫到那個xml中呢??

    不管是寫到RoleMapper.xml還是UserRoleMapper.xml,或者是一個獨立的XxxMapper.xml中。如果使用了二級緩存,都會導致上面這個查詢結果可能不正確。

    如果你正好修改了這個用戶的角色,上面這個查詢使用緩存的時候結果就是錯的。

    這點應該很容易理解。

    在我看來,就以MyBatis目前的緩存方式來看是無解的。多表操作根本不能緩存。

    如果你讓他們都使用同一個namespace(通過<cache-ref>)來避免臟數據,那就失去了緩存的意義。

    看到這里,實際上就是說,二級緩存不能用。整篇文章介紹這么多也沒什么用了。



    五、挽救二級緩存?

    想更高效率的使用二級緩存是解決不了了。

    但是解決多表操作避免臟數據還是有法解決的。解決思路就是通過攔截器判斷執行的sql涉及到那些表(可以用jsqlparser解析),然后把相關表的緩存自動清空。但是這種方式對緩存的使用效率是很低的。

    設計這樣一個插件是相當復雜的,既然我沒想著去實現,就不廢話了。

    最后還是建議,放棄二級緩存,在業務層使用可控制的緩存代替更好。

    總結

    以上是生活随笔為你收集整理的深入了解MyBatis二级缓存的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。