mybatis二級緩存關鍵字
A. mybatis自帶一級和二級緩存,為什麼還要用redis
二級緩存是namespace區域內的,所以不同的namespace下操作同一張表,會導致數據不一致,個人從未使用過二級緩存,redis更靈活,功能更豐富
B. mybatis的cache-ref怎麼使用
首先明確一點 cache-ref 只對二級緩存有效,沒有使用二級緩存時,這東西沒有意義。
以下說明只針對二級緩存。
當mybatis的當前命名空間存在DML的事務提交時,會使當前命名空間里的緩存失效,這樣在查詢時,會直接從資料庫拿到數據,並再次緩存。但是如果是多表連接查詢,如tableA join tableB,A表的DML操作在A的nameSpace,B表的DML操作在B的nameSpace,當B表在進行了數據修改時,不會使A表緩存失效,再使用tableA join tableB會直接從緩存中獲取數據,因為此時緩存沒有刷新,而且B表的數據也有變化,那麼此時讀取的就是臟數據。
此時就需要在B的nameSpace里使用<cache-ref namespace="A"/>,保證跟新B時,A的緩存也失效。再在A裡面執行 tableA join tableB時,會重新從資料庫里查詢最新數據。
C. Spring boot + Mybatis plus + Redis實現二級緩存
1.1 通過application.yml配置redis的連接信息,springboot默認redis用的lecttuce客戶端,如果想用jedis的話,只需要在pom.xml中引入redis的時候排除在lecttuce,然後再導入jedis的jar包就好了,
1.2 打開mybatis plus的二級緩存,為true的時候是開啟的,false是關閉二級緩存
1.3 編寫緩存類繼承Cache類,實現Cache中的方法
1.4 早*.xml中加上<cache>標簽,type寫你所編寫二級緩存類的路徑
D. mybatis二級緩存原理
mybatis篇
一級緩存的作用域是sqlsession級別的,也就是說不同的Sqlsession是不會走一級緩存的,那麼如果需要跨Sqlsession的緩存,就需要使用到二級緩存了。
二級緩存的話默認是關閉的,所以需要我們開啟,開啟的方式官網也有介紹,需要在mybatis-config.xml核心配置文件中開啟二級緩存功能,並且我們mapper.xml中也需要加入<cache/>標簽,二者缺一不可,後面我們看源碼就能知道為啥這兩個缺一不可。
先來看個例子
執行結果很意外,為什麼二級緩存的功能都開啟了,結果sql還是執行了2次,並沒有走緩存,其實,二級緩存還有一個要注意的點那就是必須要提交事務二級緩存才會保存記錄,因為已經是跨SqlSession共享緩存了,所以事務必須要提交,否則會讀取到因混滾導致的錯誤數據。
有個地方需要注意,二級緩存的Sqlsession中的Executor實際上是CachingExecutor
我們知道getMapper最終的執行都會走到MapperProxy類中的invoker方法,具體就來分析這個類。
最後來到了重點的地方
CacheKey我們可以認為他就是每個方法對應的一個唯一標識符。
這里我們就可以看出為什麼之前兩者必須要配置,cacheEnable開啟了才會用CachingExecutor包裝一下BaseExecutor,而<cache/>標簽只有配置了才會走緩存的邏輯
這里的tcm
到這,我們就差不多揭開了二級緩存的秘密,重要的還是<cache/>這個標簽,因為它的存在就對應著每個mapper.xml中的一個具體Cache類,而這個類在每個mapper.xml中又是同一個,所以最終的值是放入了Cache類中,key為CacheKey,value就是sql執行的結果。
至於為什麼需要事務提交才能命中二級緩存,我們看下put方法就知道
這里的putObject並沒有真正的把值存入Cache中,而是存入了待提交的Map中,所以再來看下commit做了什麼
具體看tcm.commit()
而這里可以看到此處會遍歷所有的TransactionCache並執行commit方法
真相就出來了,會遍歷待提交的Map然後把裡面的值都存入Cache中,所以後面的查詢就能直接從Cache中拿到值了。
總結
二級緩存先會把Sqlsession中的Executor包裝成包裝成CacheingExecutor,所有的sql都會經過這個類,而該類通過mapper.xml中配置的唯一<cache/>標簽生成的Cache類存放每個方法執行的結果
E. jojo那裡可以緩存
Mybatis的一個重要特性是緩存。它的緩存機制可以極大地提高應用程序的性能。Mybatis緩存的主要目的是減少資料庫虛毀訪問次數,從而提高應用程序的性能。Mybatis提供了兩種緩存:一級緩存和二級緩存。一級緩存是Mybatis默認啟用的緩存,它位於SqlSession級別,當SqlSession關閉時,一級緩存也會被清空。二級緩存是Mybatis可選擇啟用的緩存,它位於Mapper級別,可以被多個SqlSession共享,其中的數據會在多個SqlSession之間共享。Mybatis的緩存機制可以幫助減少資料庫訪問次數察盯,從而提高應敗譽和用程序的性能。
F. MyBatis二級緩存帶來的問題
MyBatis二級緩存使用的在某些場景下會出問題,來看一下為什麼這么說。
假設我有一條select語句(開啟了二級緩存):
selecta.col1, a.col2, a.col3, b.col1, b.col2, b.col3fromtableA a, tableB bwherea.id= b.id;
對於tableA與tableB的操作定義在兩個Mapper中,分別叫做MapperA與MapperB,即它們屬於兩個命名空間,如果此時啟用緩存:
MapperA中執行上述sql語句查詢這6個欄位
tableB更新了col1與col2兩個欄位
MapperA再次執行上述sql語句查詢這6個欄位(前提是沒有執行過任何insert、delete、update操作)
此時問題就來了,即使第(2)步tableB更新了col1與col2兩個欄位,第(3)步MapperA走二級緩存查詢到的這6個欄位依然是原來的這6個欄位的值,因為我們從CacheKey的3組條件來看:
<select>標簽所在的Mapper的Namespace+<select>標簽的id屬性
RowBounds的offset和limit屬性,RowBounds是MyBatis用於處理分頁的一個類,offset默認為0,limit默認為Integer.MAX_VALUE
<select>標簽中定義的sql語句
對於MapperA來說,其中的任何一個條件都沒有變化,自然會將原結果返回。
這個問題對於MyBatis的二級緩存來說是一個無解的問題,因此使用MyBatis二級緩存有一個前提: 必須保證所有的增刪改查都在同一個命名空間下才行 。
G. mybatis 的二級緩存與延遲載入啥時候用
Hibernate與Mybatis對比總結【兩者相同點】Hibernate與MyBatis都可以是通過SessionFactoryBuider由XML配置文件生成SessionFactory,然後由SessionFactory生成Session,最後由Session來開啟執行事務和SQL語句。其中SessionFactoryBuider,SessionFactory,Session的生命周期都是差不多的。Hibernate和MyBatis都支持JDBC和JTA事務處理。【Mybatis優勢】MyBatis可以進行更為細致的SQL優化,可以減少查詢欄位。MyBatis容易掌握,而Hibernate門檻較高。【Hibernate優勢】Hibernate的DAO層開發比MyBatis簡單,Mybatis需要維護SQL和結果映射。Hibernate對對象的維護和緩存要比MyBatis好,對增刪改查的對象的維護要方便。Hibernate資料庫移植性很好,MyBatis的資料庫移植性不好,不同的資料庫需要寫不同SQL。Hibernate有更好的二級緩存機制,可以使用第三方緩存。MyBatis本身提供的緩存機制不佳。
H. 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,就一定會涉及到多表的操作。
<select id="selectUserRoles" resultType="UserRoleVO">
select * from user_role a,role b where a.roleid = b.roleid and a.userid = #{userid}
</select>123123
像上面這個查詢,你會寫到那個xml中呢??
不管是寫到RoleMapper.xml還是UserRoleMapper.xml,或者是一個獨立的XxxMapper.xml中。如果使用了二級緩存,都會導致上面這個查詢結果可能不正確。
如果你正好修改了這個用戶的角色,上面這個查詢使用緩存的時候結果就是錯的。
這點應該很容易理解。
在我看來,就以MyBatis目前的緩存方式來看是無解的。多表操作根本不能緩存。
如果你讓他們都使用同一個namespace(通過<cache-ref>)來避免臟數據,那就失去了緩存的意義。
看到這里,實際上就是說,二級緩存不能用。整篇文章介紹這么多也沒什麼用了。
五、挽救二級緩存?
想更高效率的使用二級緩存是解決不了了。
但是解決多表操作避免臟數據還是有法解決的。解決思路就是通過攔截器判斷執行的sql涉及到那些表(可以用jsqlparser解析),然後把相關表的緩存自動清空。但是這種方式對緩存的使用效率是很低的。
設計這樣一個插件是相當復雜的,既然我沒想著去實現,就不廢話了。
最後還是建議,放棄二級緩存,在業務層使用可控制的緩存代替更好。
I. redis mybatis 二級緩存 為什麼必須要有 <property name="connectionfactory" ref
1、mybatis的二級緩存的范圍是命名空間(namespace)
2、只要這個命名空間下有一個 insert、update、delete mybatis 就會把這個命名空間下的二級緩清空。
3、如果同一個sql在不同的命名空間下,就會出現臟數據,因為一個insert、update、deleted 了另一個可能還使用者緩存數據,這樣就會出現數據的不一致性。
4、如果更新、刪除、插入的頻率比較高的話,就會刪除所有緩存在添加所有緩存在刪除,這樣緩存的命中率很低或者說根本就起不到緩存作用而且會消耗資源。
所以在沒解決這個問題的前提下,還是不提倡使用二級緩存。