带你彻底搞懂MyBatis的底层实现之缓存模块(Cache)-吊打面试官必备技能
??基礎(chǔ)支持層位于MyBatis整體架構(gòu)的最底層,支撐著MyBatis的核心處理層,是整個(gè)框架的基石。基礎(chǔ)支持層中封裝了多個(gè)較為通用的、獨(dú)立的模塊。不僅僅為MyBatis提供基礎(chǔ)支撐,也可以在合適的場景中直接復(fù)用。
??上篇文章我們給大家聊了下binding模塊,本篇文章我們重點(diǎn)來聊下緩存(Cache)模塊。
緩存模塊
??MyBatis作為一個(gè)強(qiáng)大的持久層框架,緩存是其必不可少的功能之一,Mybatis中的緩存分為一級緩存和二級緩存。但本質(zhì)上是一樣的,都是使用Cache接口實(shí)現(xiàn)的。緩存位于 org.apache.ibatis.cache包下。
??通過結(jié)構(gòu)我們能夠發(fā)現(xiàn)Cache其實(shí)使用到了裝飾器模式來實(shí)現(xiàn)緩存的處理。首先大家需要先回顧下裝飾器模式的相關(guān)內(nèi)容哦。我們先來看看Cache中的基礎(chǔ)類的API
// 煎餅加雞蛋加香腸
“裝飾者模式(Decorator Pattern)是指在不改變原有對象的基礎(chǔ)之上,將功能附加到對象上,提供了比繼承更有彈性的替代方案(擴(kuò)展原有對象的功能)。”
1 Cache接口
??Cache接口是緩存模塊中最核心的接口,它定義了所有緩存的基本行為,Cache接口的定義如下:
public interface Cache {/*** 緩存對象的 ID* @return The identifier of this cache*/String getId();/*** 向緩存中添加數(shù)據(jù),一般情況下 key是CacheKey value是查詢結(jié)果* @param key Can be any object but usually it is a {@link CacheKey}* @param value The result of a select.*/void putObject(Object key, Object value);/*** 根據(jù)指定的key,在緩存中查找對應(yīng)的結(jié)果對象* @param key The key* @return The object stored in the cache.*/Object getObject(Object key);/*** As of 3.3.0 this method is only called during a rollback* for any previous value that was missing in the cache.* This lets any blocking cache to release the lock that* may have previously put on the key.* A blocking cache puts a lock when a value is null* and releases it when the value is back again.* This way other threads will wait for the value to be* available instead of hitting the database.* 刪除key對應(yīng)的緩存數(shù)據(jù)** @param key The key* @return Not used*/Object removeObject(Object key);/*** Clears this cache instance.* 清空緩存*/void clear();/*** Optional. This method is not called by the core.* 緩存的個(gè)數(shù)。* @return The number of elements stored in the cache (not its capacity).*/int getSize();/*** Optional. As of 3.2.6 this method is no longer called by the core.* <p>* Any locking needed by the cache must be provided internally by the cache provider.* 獲取讀寫鎖* @return A ReadWriteLock*/default ReadWriteLock getReadWriteLock() {return null;}}??Cache接口的實(shí)現(xiàn)類很多,但是大部分都是裝飾器,只有PerpetualCache提供了Cache接口的基本實(shí)現(xiàn)。
2 PerpetualCache
??PerpetualCache在緩存模塊中扮演了ConcreteComponent的角色,其實(shí)現(xiàn)比較簡單,底層使用HashMap記錄緩存項(xiàng),具體的實(shí)現(xiàn)如下:
/*** 在裝飾器模式用 用來被裝飾的對象* 緩存中的 基本緩存處理的實(shí)現(xiàn)* 其實(shí)就是一個(gè) HashMap 的基本操作* @author Clinton Begin*/ public class PerpetualCache implements Cache {private final String id; // Cache 對象的唯一標(biāo)識// 用于記錄緩存的Map對象private final Map<Object, Object> cache = new HashMap<>();public PerpetualCache(String id) {this.id = id;}@Overridepublic String getId() {return id;}@Overridepublic int getSize() {return cache.size();}@Overridepublic void putObject(Object key, Object value) {cache.put(key, value);}@Overridepublic Object getObject(Object key) {return cache.get(key);}@Overridepublic Object removeObject(Object key) {return cache.remove(key);}@Overridepublic void clear() {cache.clear();}@Overridepublic boolean equals(Object o) {if (getId() == null) {throw new CacheException("Cache instances require an ID.");}if (this == o) {return true;}if (!(o instanceof Cache)) {return false;}Cache otherCache = (Cache) o;// 只關(guān)心IDreturn getId().equals(otherCache.getId());}@Overridepublic int hashCode() {if (getId() == null) {throw new CacheException("Cache instances require an ID.");}// 只關(guān)心IDreturn getId().hashCode();}}??然后我們可以來看看cache.decorators包下提供的裝飾器。他們都實(shí)現(xiàn)了Cache接口。這些裝飾器都在PerpetualCache的基礎(chǔ)上提供了一些額外的功能,通過多個(gè)組合實(shí)現(xiàn)一些特殊的需求。
3 BlockingCache
??通過名稱我們能看出來是一個(gè)阻塞同步的緩存,它保證只有一個(gè)線程到緩存中查找指定的key對應(yīng)的數(shù)據(jù)。
public class BlockingCache implements Cache {private long timeout; // 阻塞超時(shí)時(shí)長private final Cache delegate; // 被裝飾的底層 Cache 對象// 每個(gè)key 都有對象的 ReentrantLock 對象private final ConcurrentHashMap<Object, ReentrantLock> locks;public BlockingCache(Cache delegate) {// 被裝飾的 Cache 對象this.delegate = delegate;this.locks = new ConcurrentHashMap<>();}@Overridepublic String getId() {return delegate.getId();}@Overridepublic int getSize() {return delegate.getSize();}@Overridepublic void putObject(Object key, Object value) {try {// 執(zhí)行 被裝飾的 Cache 中的方法delegate.putObject(key, value);} finally {// 釋放鎖releaseLock(key);}}@Overridepublic Object getObject(Object key) {acquireLock(key); // 獲取鎖Object value = delegate.getObject(key); // 獲取緩存數(shù)據(jù)if (value != null) { // 有數(shù)據(jù)就釋放掉鎖,否則繼續(xù)持有鎖releaseLock(key);}return value;}@Overridepublic Object removeObject(Object key) {// despite of its name, this method is called only to release locksreleaseLock(key);return null;}@Overridepublic void clear() {delegate.clear();}private ReentrantLock getLockForKey(Object key) {return locks.computeIfAbsent(key, k -> new ReentrantLock());}private void acquireLock(Object key) {Lock lock = getLockForKey(key);if (timeout > 0) {try {boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS);if (!acquired) {throw new CacheException("Couldn't get a lock in " + timeout + " for the key " + key + " at the cache " + delegate.getId());}} catch (InterruptedException e) {throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);}} else {lock.lock();}}private void releaseLock(Object key) {ReentrantLock lock = locks.get(key);if (lock.isHeldByCurrentThread()) {lock.unlock();}}public long getTimeout() {return timeout;}public void setTimeout(long timeout) {this.timeout = timeout;} }??通過源碼我們能夠發(fā)現(xiàn),BlockingCache本質(zhì)上就是在我們操作緩存數(shù)據(jù)的前后通過 ReentrantLock對象來實(shí)現(xiàn)了加鎖和解鎖操作。其他的具體實(shí)現(xiàn)類,大家可以自行查閱
| 基本緩存 | 緩存基本實(shí)現(xiàn)類 | 默認(rèn)是PerpetualCache,也可以自定義比如RedisCache、EhCache等,具備基本功能的緩存類 | 無 |
| LruCache | LRU策略的緩存 | 當(dāng)緩存到達(dá)上限時(shí)候,刪除最近最少使用的緩存(Least Recently Use) | eviction=“LRU”(默認(rèn)) |
| FifoCache | FIFO策略的緩存 | 當(dāng)緩存到達(dá)上限時(shí)候,刪除最先入隊(duì)的緩存 | eviction=“FIFO” |
| SoftCacheWeakCache | 帶清理策略的緩存 | 通過JVM的軟引用和弱引用來實(shí)現(xiàn)緩存,當(dāng)JVM內(nèi)存不足時(shí),會(huì)自動(dòng)清理掉這些緩存,基于SoftReference和WeakReference | eviction="SOFT"eviction=“WEAK” |
| LoggingCache | 帶日志功能的緩存 | 比如:輸出緩存命中率 | 基本 |
| SynchronizedCache | 同步緩存 | 基于synchronized關(guān)鍵字實(shí)現(xiàn),解決并發(fā)問題 | 基本 |
| BlockingCache | 阻塞緩存 | 通過在get/put方式中加鎖,保證只有一個(gè)線程操作緩存,基于Java重入鎖實(shí)現(xiàn) | blocking=true |
| SerializedCache | 支持序列化的緩存 | 將對象序列化以后存到緩存中,取出時(shí)反序列化 | readOnly=false(默認(rèn)) |
| ScheduledCache | 定時(shí)調(diào)度的緩存 | 在進(jìn)行g(shù)et/put/remove/getSize等操作前,判斷緩存時(shí)間是否超過了設(shè)置的最長緩存時(shí)間(默認(rèn)是一小時(shí)),如果是則清空緩存–即每隔一段時(shí)間清空一次緩存 | flushInterval不為空 |
| TransactionalCache | 事務(wù)緩存 | 在二級緩存中使用,可一次存入多個(gè)緩存,移除多個(gè)緩存 | 在TransactionalCacheManager中用Map維護(hù)對應(yīng)關(guān)系 |
4 緩存的應(yīng)用
4.1 緩存對應(yīng)的初始化
??在Configuration初始化的時(shí)候會(huì)為我們的各種Cache實(shí)現(xiàn)注冊對應(yīng)的別名
在解析settings標(biāo)簽的時(shí)候,設(shè)置的默認(rèn)值有如下
cacheEnabled默認(rèn)為true,localCacheScope默認(rèn)為 SESSION
在解析映射文件的時(shí)候會(huì)解析我們相關(guān)的cache標(biāo)簽
然后解析映射文件的cache標(biāo)簽后會(huì)在Configuration對象中添加對應(yīng)的數(shù)據(jù)在
private void cacheElement(XNode context) {// 只有 cache 標(biāo)簽不為空才解析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);}}繼續(xù)
然后我們可以發(fā)現(xiàn) 如果存儲(chǔ) cache 標(biāo)簽,那么對應(yīng)的 Cache對象會(huì)被保存在 currentCache 屬性中。
進(jìn)而在 Cache 對象 保存在了 MapperStatement 對象的 cache 屬性中。
然后我們再看看openSession的時(shí)候又做了哪些操作,在創(chuàng)建對應(yīng)的執(zhí)行器的時(shí)候會(huì)有緩存的操作
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {executorType = executorType == null ? defaultExecutorType : executorType;executorType = executorType == null ? ExecutorType.SIMPLE : executorType;Executor executor;if (ExecutorType.BATCH == executorType) {executor = new BatchExecutor(this, transaction);} else if (ExecutorType.REUSE == executorType) {executor = new ReuseExecutor(this, transaction);} else {// 默認(rèn) SimpleExecutorexecutor = new SimpleExecutor(this, transaction);}// 二級緩存開關(guān),settings 中的 cacheEnabled 默認(rèn)是 trueif (cacheEnabled) {executor = new CachingExecutor(executor);}// 植入插件的邏輯,至此,四大對象已經(jīng)全部攔截完畢executor = (Executor) interceptorChain.pluginAll(executor);return executor;}??也就是如果 cacheEnabled 為 true 就會(huì)通過 CachingExecutor 來裝飾executor 對象,然后就是在執(zhí)行SQL操作的時(shí)候會(huì)涉及到緩存的具體使用。這個(gè)就分為一級緩存和二級緩存,這個(gè)我們來分別介紹
4.2 一級緩存
??一級緩存也叫本地緩存(Local Cache),MyBatis的一級緩存是在會(huì)話(SqlSession)層面進(jìn)行緩存的。MyBatis的一級緩存是默認(rèn)開啟的,不需要任何的配置(如果要關(guān)閉,localCacheScope設(shè)置為STATEMENT)。在BaseExecutor對象的query方法中有關(guān)閉一級緩存的邏輯
??然后我們需要考慮下在一級緩存中的 PerpetualCache 對象在哪創(chuàng)建的,因?yàn)橐患壘彺媸荢ession級別的緩存,肯定需要在Session范圍內(nèi)創(chuàng)建,其實(shí)PerpetualCache的實(shí)例化是在BaseExecutor的構(gòu)造方法中創(chuàng)建的
??一級緩存的具體實(shí)現(xiàn)也是在BaseExecutor的query方法中來實(shí)現(xiàn)的
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {// 異常體系之 ErrorContextErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());if (closed) {throw new ExecutorException("Executor was closed.");}if (queryStack == 0 && ms.isFlushCacheRequired()) {// flushCache="true"時(shí),即使是查詢,也清空一級緩存clearLocalCache();}List<E> list;try {// 防止遞歸查詢重復(fù)處理緩存queryStack++;// 查詢一級緩存// ResultHandler 和 ResultSetHandler的區(qū)別list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;if (list != null) {handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);} else {// 真正的查詢流程list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);}} finally {queryStack--;}if (queryStack == 0) {for (DeferredLoad deferredLoad : deferredLoads) {deferredLoad.load();}// issue #601deferredLoads.clear();if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {// issue #482clearLocalCache();}}return list;}一級緩存的驗(yàn)證:
同一個(gè)Session中的多個(gè)相同操作
@Testpublic void test1() throws Exception{// 1.獲取配置文件InputStream in = Resources.getResourceAsStream("mybatis-config.xml");// 2.加載解析配置文件并獲取SqlSessionFactory對象SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);// 3.根據(jù)SqlSessionFactory對象獲取SqlSession對象SqlSession sqlSession = factory.openSession();// 4.通過SqlSession中提供的 API方法來操作數(shù)據(jù)庫List<User> list = sqlSession.selectList("com.gupaoedu.mapper.UserMapper.selectUserList");System.out.println(list.size());// 一級緩存測試System.out.println("---------");list = sqlSession.selectList("com.gupaoedu.mapper.UserMapper.selectUserList");System.out.println(list.size());// 5.關(guān)閉會(huì)話sqlSession.close();}輸出日志
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@477b4cdf] ==> Preparing: select * from t_user ==> Parameters: <== Columns: id, user_name, real_name, password, age, d_id <== Row: 1, zhangsan, 張三, 123456, 18, null <== Row: 2, lisi, 李四, 11111, 19, null <== Row: 3, wangwu, 王五, 111, 22, 1001 <== Row: 4, wangwu, 王五, 111, 22, 1001 <== Row: 5, wangwu, 王五, 111, 22, 1001 <== Row: 6, wangwu, 王五, 111, 22, 1001 <== Row: 7, wangwu, 王五, 111, 22, 1001 <== Row: 8, aaa, bbbb, null, null, null <== Row: 9, aaa, bbbb, null, null, null <== Row: 10, aaa, bbbb, null, null, null <== Row: 11, aaa, bbbb, null, null, null <== Row: 12, aaa, bbbb, null, null, null <== Row: 666, hibernate, 持久層框架, null, null, null <== Total: 13 13 --------- 13可以看到第二次查詢沒有經(jīng)過數(shù)據(jù)庫操作
不同Session的相同操作
@Testpublic void test2() throws Exception{// 1.獲取配置文件InputStream in = Resources.getResourceAsStream("mybatis-config.xml");// 2.加載解析配置文件并獲取SqlSessionFactory對象SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);// 3.根據(jù)SqlSessionFactory對象獲取SqlSession對象SqlSession sqlSession = factory.openSession();// 4.通過SqlSession中提供的 API方法來操作數(shù)據(jù)庫List<User> list = sqlSession.selectList("com.gupaoedu.mapper.UserMapper.selectUserList");System.out.println(list.size());sqlSession.close();sqlSession = factory.openSession();// 一級緩存測試System.out.println("---------");list = sqlSession.selectList("com.gupaoedu.mapper.UserMapper.selectUserList");System.out.println(list.size());// 5.關(guān)閉會(huì)話sqlSession.close();}輸出結(jié)果
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@477b4cdf] ==> Preparing: select * from t_user ==> Parameters: <== Columns: id, user_name, real_name, password, age, d_id <== Row: 1, zhangsan, 張三, 123456, 18, null <== Row: 2, lisi, 李四, 11111, 19, null <== Row: 3, wangwu, 王五, 111, 22, 1001 <== Row: 4, wangwu, 王五, 111, 22, 1001 <== Row: 5, wangwu, 王五, 111, 22, 1001 <== Row: 6, wangwu, 王五, 111, 22, 1001 <== Row: 7, wangwu, 王五, 111, 22, 1001 <== Row: 8, aaa, bbbb, null, null, null <== Row: 9, aaa, bbbb, null, null, null <== Row: 10, aaa, bbbb, null, null, null <== Row: 11, aaa, bbbb, null, null, null <== Row: 12, aaa, bbbb, null, null, null <== Row: 666, hibernate, 持久層框架, null, null, null <== Total: 13 13 Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@477b4cdf] Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@477b4cdf] Returned connection 1199262943 to pool. --------- Opening JDBC Connection Checked out connection 1199262943 from pool. Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@477b4cdf] ==> Preparing: select * from t_user ==> Parameters: <== Columns: id, user_name, real_name, password, age, d_id <== Row: 1, zhangsan, 張三, 123456, 18, null <== Row: 2, lisi, 李四, 11111, 19, null <== Row: 3, wangwu, 王五, 111, 22, 1001 <== Row: 4, wangwu, 王五, 111, 22, 1001 <== Row: 5, wangwu, 王五, 111, 22, 1001 <== Row: 6, wangwu, 王五, 111, 22, 1001 <== Row: 7, wangwu, 王五, 111, 22, 1001 <== Row: 8, aaa, bbbb, null, null, null <== Row: 9, aaa, bbbb, null, null, null <== Row: 10, aaa, bbbb, null, null, null <== Row: 11, aaa, bbbb, null, null, null <== Row: 12, aaa, bbbb, null, null, null <== Row: 666, hibernate, 持久層框架, null, null, null <== Total: 13 13 Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@477b4cdf] Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@477b4cdf] Returned connection 1199262943 to pool.通過輸出我們能夠發(fā)現(xiàn),不同的Session中的相同操作,一級緩存是沒有起作用的。
4.3 二級緩存
??二級緩存是用來解決一級緩存不能跨會(huì)話共享的問題的,范圍是namespace級別的,可以被多個(gè)SqlSession共享(只要是同一個(gè)接口里面的相同方法,都可以共享),生命周期和應(yīng)用同步。
??二級緩存的設(shè)置,首先是settings中的cacheEnabled要設(shè)置為true,當(dāng)然默認(rèn)的就是為true,這個(gè)步驟決定了在創(chuàng)建Executor對象的時(shí)候是否通過CachingExecutor來裝飾。
??那么設(shè)置了cacheEnabled標(biāo)簽為true是否就意味著 二級緩存是否一定可用呢?當(dāng)然不是,我們還需要在 對應(yīng)的映射文件中添加 cache 標(biāo)簽才行。
<!-- 聲明這個(gè)namespace使用二級緩存 --> <cache type="org.apache.ibatis.cache.impl.PerpetualCache"size="1024" <!—最多緩存對象個(gè)數(shù),默認(rèn)1024-->eviction="LRU" <!—回收策略-->flushInterval="120000" <!—自動(dòng)刷新時(shí)間 ms,未配置時(shí)只有調(diào)用時(shí)刷新-->readOnly="false"/> <!—默認(rèn)是false(安全),改為true可讀寫時(shí),對象必須支持序列化 -->cache屬性詳解:
| type | 緩存實(shí)現(xiàn)類 | 需要實(shí)現(xiàn)Cache接口,默認(rèn)是PerpetualCache,可以使用第三方緩存 |
| size | 最多緩存對象個(gè)數(shù) | 默認(rèn)1024 |
| eviction | 回收策略(緩存淘汰算法) | LRU – 最近最少使用的:移除最長時(shí)間不被使用的對象(默認(rèn))。FIFO – 先進(jìn)先出:按對象進(jìn)入緩存的順序來移除它們。SOFT – 軟引用:移除基于垃圾回收器狀態(tài)和軟引用規(guī)則的對象。WEAK – 弱引用:更積極地移除基于垃圾收集器狀態(tài)和弱引用規(guī)則的對象。 |
| flushInterval | 定時(shí)自動(dòng)清空緩存間隔 | 自動(dòng)刷新時(shí)間,單位 ms,未配置時(shí)只有調(diào)用時(shí)刷新 |
| readOnly | 是否只讀 | true:只讀緩存;會(huì)給所有調(diào)用者返回緩存對象的相同實(shí)例。因此這些對象不能被修改。這提供了很重要的性能優(yōu)勢。false:讀寫緩存;會(huì)返回緩存對象的拷貝(通過序列化),不會(huì)共享。這會(huì)慢一些,但是安全,因此默認(rèn)是 false。改為false可讀寫時(shí),對象必須支持序列化。 |
| blocking | 啟用阻塞緩存 | 通過在get/put方式中加鎖,保證只有一個(gè)線程操作緩存,基于Java重入鎖實(shí)現(xiàn) |
再來看下cache標(biāo)簽在源碼中的體現(xiàn),創(chuàng)建cacheKey
@Overridepublic <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {// 獲取SQLBoundSql boundSql = ms.getBoundSql(parameterObject);// 創(chuàng)建CacheKey:什么樣的SQL是同一條SQL? >>CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}createCacheKey自行進(jìn)去查看
而這看到的和我們前面在緩存初始化時(shí)看到的 cache 標(biāo)簽解析操作是對應(yīng)上的。所以我們要開啟二級緩存兩個(gè)條件都要滿足。
??這樣的設(shè)置表示當(dāng)前的映射文件中的相關(guān)查詢操作都會(huì)觸發(fā)二級緩存,但如果某些個(gè)別方法我們不希望走二級緩存怎么辦呢?我們可以在標(biāo)簽中添加一個(gè) useCache=false 來實(shí)現(xiàn)的設(shè)置不使用二級緩存
還有就是當(dāng)我們執(zhí)行的對應(yīng)的DML操作,在MyBatis中會(huì)清空對應(yīng)的二級緩存和一級緩存。
在解析映射文件的時(shí)候DML操作flushCacheRequired為true
4.4 第三方緩存
???在實(shí)際開發(fā)的時(shí)候我們一般也很少使用MyBatis自帶的二級緩存,這時(shí)我們會(huì)使用第三方的緩存工具Ehcache獲取Redis來實(shí)現(xiàn),那么他們是如何來實(shí)現(xiàn)的呢?
https://github.com/mybatis/redis-cache
添加依賴
<dependency><groupId>org.mybatis.caches</groupId><artifactId>mybatis-redis</artifactId><version>1.0.0-beta2</version> </dependency>然后加上Cache標(biāo)簽的配置
<cache type="org.mybatis.caches.redis.RedisCache"eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>然后添加redis的屬性文件
host=192.168.100.120 port=6379 connectionTimeout=5000 soTimeout=5000 database=0測試效果
數(shù)據(jù)存儲(chǔ)到了Redis中
??然后大家也可以自行分析下第三方的Cache是如何替換掉PerpetualCache的,因?yàn)镻erpetualCache是基于HashMap處理的,而RedisCache是基于Redis來存儲(chǔ)緩存數(shù)據(jù)的。
提示
緩存模塊我們就介紹到此。 然后大家可以基于我們上面所介紹的基礎(chǔ)支持層,再系統(tǒng)的來梳理下核心處理層的流程
~~ 好了,緩存模塊的內(nèi)容就給大家介紹到這里,如果對你有幫助,歡迎點(diǎn)贊關(guān)注加收藏
下篇我們介紹 MyBatis中的插件機(jī)制,敬請期待 V_V
總結(jié)
以上是生活随笔為你收集整理的带你彻底搞懂MyBatis的底层实现之缓存模块(Cache)-吊打面试官必备技能的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++之动态联编
- 下一篇: c语言如何输出字母锥子塔,C語言课堂练习