自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

深入探究 MyBatis 緩存機(jī)制

開發(fā)
本文我們將一同踏上探索 MyBatis 緩存的奇妙之旅,我們將逐步揭開它神秘的面紗,深入剖析其背后的原理、結(jié)構(gòu)和運作方式。

在當(dāng)今的軟件開發(fā)領(lǐng)域,性能優(yōu)化始終是一個備受關(guān)注的核心議題。而在眾多提升性能的技術(shù)手段中,MyBatis 緩存無疑占據(jù)著重要的一席之地。當(dāng)我們深入探索 MyBatis 的世界時,會發(fā)現(xiàn)其緩存機(jī)制宛如一座隱藏的寶藏,蘊(yùn)含著巨大的潛力和價值。

在接下來的篇章中,我們將一同踏上探索 MyBatis 緩存的奇妙之旅。我們將逐步揭開它神秘的面紗,深入剖析其背后的原理、結(jié)構(gòu)和運作方式。通過了解它是如何巧妙地減少數(shù)據(jù)庫查詢次數(shù)、提升系統(tǒng)響應(yīng)速度,我們能更好地把握這一強(qiáng)大工具,為我們的開發(fā)項目帶來更卓越的性能表現(xiàn)。

一、詳解一級緩存

1. 什么是一級緩存

當(dāng)我們建立SqlSession時,就可以通過Mybatis進(jìn)行sql查詢,假如本次session查詢時我們需要進(jìn)行兩次相同的sql查詢,就需要進(jìn)行進(jìn)行兩次的磁盤IO,為了避免這種沒必要的等待,Mybatis為每一個SqlSession設(shè)置一級緩存,在同一個SqlSession中,一級緩存會將第一次查詢結(jié)果緩存起來,第二次相同的查詢就可以直接使用了。

2. 一級緩存使用示例

Mybatis默認(rèn)是開啟一級緩存的,如下所示,可以發(fā)現(xiàn)只要第二次使用的sql和參數(shù)一樣,就會從一級緩存中獲取數(shù)據(jù)。

 User1 user1 = user1Mapper.select("1");
        logger.info("一級緩存第一次查詢:[{}]", user1);

        User1 user11 = user1Mapper.select("1");
        logger.info("一級緩存第二次查詢:[{}]", user11);


        User1 user12 = user1Mapper.select("2");
        logger.info("一級緩存第三次查詢,id不同:[{}]", user12);

輸出結(jié)果:

2022-11-27 15:51:28,313 [main] DEBUG [com.sharkchili.mapper.User1Mapper.select] - ==>  Preparing: select * from user1 where id = ?
2022-11-27 15:51:28,338 [main] DEBUG [com.sharkchili.mapper.User1Mapper.select] - ==> Parameters: 1(String)
2022-11-27 15:51:28,539 [main] DEBUG [com.sharkchili.mapper.User1Mapper.select] - <==      Total: 1
2022-11-27 15:51:28,541 [main] DEBUG [com.sharkchili.mapper.User1Mapper.select] - ==>  Preparing: select * from user1 where id = ?
 2022-11-27 15:51:28,541 [main] DEBUG [com.sharkchili.mapper.User1Mapper.select] - ==> Parameters: 2(String)
[main] INFO com.sharkchili.mapper.MyBatisTest - 一級緩存第一次查詢:[User1{id='1', name='小明', user2=null}]



[main] INFO com.sharkchili.mapper.MyBatisTest - 一級緩存第二次查詢:[User1{id='1', name='小明', user2=null}]
 2022-11-27 15:51:28,667 [main] DEBUG [com.sharkchili.mapper.User1Mapper.select] - <==      Total: 1
[main] INFO com.sharkchili.mapper.MyBatisTest - 一級緩存第三次查詢,id不同:[User1{id='2', name='小王', user2=null}]    

3. 一級緩存的執(zhí)行過程

當(dāng)然我們也得有一個查詢代碼,查詢代碼如下所示:

 User1 user1 = user1Mapper.select("1");
        logger.info("一級緩存第一次查詢:[{}]", user1);

本質(zhì)上mapper代理對象進(jìn)行查詢操作時底層的BaseExecutor會調(diào)用queryFromDatabase獲取查詢結(jié)果,然后將查詢結(jié)果存到緩存中:

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);

        List list;
        try {
        //執(zhí)行并獲取查詢結(jié)果
            list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
            this.localCache.removeObject(key);
        }

  //基于本次查詢用到的MappedStatement , 參數(shù), rowBounds, sql作為key將結(jié)果緩存
        this.localCache.putObject(key, list);
        if (ms.getStatementType() == StatementType.CALLABLE) {
            this.localOutputParameterCache.putObject(key, parameter);
        }

        return list;
    }

總結(jié)一下流程就如下圖所示:

4. 一級緩存的生命周期

  • 當(dāng)SqlSession調(diào)用了close之后,會直接釋放PerpetualCache對象,緩存自然不能使用了。
  • 進(jìn)行update、delete、insert等操作,緩存就會被清空,但是緩存對象還能用。
  • 調(diào)用clearCache同理,緩存被清空,但是對象還能用。

二、詳解二級緩存

1. 什么是二級緩存

二級緩存是mybatis為了解決跨session緩存數(shù)據(jù)所增加的一層面向namespace級別的緩存方案,即以mapper文件為單位劃分的緩存空間,通過開啟二級緩存,程序執(zhí)行查詢時會優(yōu)先從全局共享的的二級緩存開始查詢,如果全局的二級緩存沒有數(shù)據(jù),再通過一級緩存查詢,如果有則返回并返回,如果沒有則執(zhí)行SQL查詢依次緩存到一級緩存、二級緩存中:

2. 二級緩存使用示例

為了討論二級緩存,我們不妨展示一個簡單的二級緩存配置示例,首先Mybatis配置開啟二級緩存,其實這個可以不用配置,默認(rèn)的情況下是true:

 <settings>
    
        <!--開啟二級緩存-->
        <setting name="cacheEnabled" value="true"/>
    </settings>

對應(yīng)的Mapper文件下添加下面這段配置:

    <cache/>

然后我們給出對的測試代碼:

   UserMapper userMapper = SpringUtil.getBean(UserMapper.class);
        User user = new User();
        user.setId(1L);
        //第一次查詢
        User u = userMapper.selectByUserId(user);
        log.info("user:{}", JSONUtil.toJsonStr(u));
        //第二次查詢
        User u2 = userMapper.selectByUserId(user);
        log.info("user:{}", JSONUtil.toJsonStr(u2));

可以看到使用同樣的會話,第二次查詢不會查詢SQL而是直接從二級緩存獲取數(shù)據(jù):

2024-12-13 12:13:00.458  INFO 17996 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2024-12-13 12:13:00.464 DEBUG 17996 --- [           main] c.s.mapper.UserMapper.selectByUserId     : ==>  Preparing: select u.id id, u.name name , m.total total from user u inner join money m on u.id = m.u_id where u.id = 1
2024-12-13 12:13:00.482 DEBUG 17996 --- [           main] c.s.mapper.UserMapper.selectByUserId     : ==> Parameters: 
2024-12-13 12:13:00.498 DEBUG 17996 --- [           main] c.s.mapper.UserMapper.selectByUserId     : <==      Total: 1
2024-12-13 12:13:00.549  INFO 17996 --- [           main] com.sharkChili.WebApplication            : user:{"id":1,"name":"xiaoming","total":50}



2024-12-13 12:13:00.550 DEBUG 17996 --- [           main] com.sharkChili.mapper.UserMapper         : Cache Hit Ratio [com.sharkChili.mapper.UserMapper]: 0.5
2024-12-13 12:13:00.550  INFO 17996 --- [           main] com.sharkChili.WebApplication            : user:{"id":1,"name":"xiaoming","total":50}

3. 二級緩存的工作模式

在開啟二級緩存配置后,框架會首先去CachingExecutor看看是否有緩存數(shù)據(jù),若沒有則會從一級緩存查詢,實在找不到就通過BaseExecutor查詢并處理完緩存起來。

注意這里CachingExecutor用到了裝飾者模式,將Executor 組合進(jìn)來,所以CachingExecutor會先調(diào)用(List)this.tcm.getObject(cache, key);看看緩存中是否有數(shù)據(jù),若沒有在進(jìn)行進(jìn)一步查詢并緩存的操作。

//將基礎(chǔ)執(zhí)行器作為被裝飾的成員屬性組合進(jìn)來
 private final Executor delegate;

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        Cache cache = ms.getCache();
        if (cache != null) {
            this.flushCacheIfRequired(ms);
            //開啟二級緩存則執(zhí)行該邏輯
            if (ms.isUseCache() && resultHandler == null) {
                this.ensureNoOutParams(ms, boundSql);
                //先去緩存查詢
                List<E> list = (List)this.tcm.getObject(cache, key);
                if (list == null) {
                // 若為空則調(diào)用BaseExecutor 進(jìn)行數(shù)據(jù)獲取
                    list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                    //將數(shù)據(jù)存到二級緩存中
                    this.tcm.putObject(cache, key, list);
                }

                return list;
            }
        }
  //調(diào)用BaseExecutor 獲取查詢結(jié)果并緩存
        return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }

4. 二級緩存怎么作用域

二級緩存怎么作用域有兩種:

  • 自定義劃分,我們在每個Mapper.xml中添加 <cache/>使得每一個mapper都有一個全局的獨立緩存空間:

  • 假如我們希望多個mapper共享一個空間的話,需要被分享的mapper使用<cache/>,而其他mapper則用<cache-ref namespace="">指向這個空間即可。

5. 使用二級緩存要具備的幾個條件

總的來說是三個條件:

  • 全局配置開啟二級緩存:<setting name="cacheEnabled" value="true"/>,默認(rèn)是true的。
  • mapper.xml標(biāo)簽配置了 <cache/>或者 <cache-ref/>
  • select語句配置useCache=true

6. 二級緩存實現(xiàn)的選擇有哪些和默認(rèn)項

有三種吧:

  • 框架自身提供了很多緩存方案,這些緩存還提供了不同的回收策略:例如LRU、FIFO等。
  • 用戶繼承接口org.apache.ibatis.cache.Cache自行實現(xiàn)一個緩存。
  • 通過第三方緩存工具集成。

對于Mybatis的二級緩存默認(rèn)緩存算法,如下圖,可以看到框架自身基于裝飾者模式實現(xiàn)了很多緩存工具,并且每個緩存容量都有限制,不同的緩存工具內(nèi)存回收策略是不同的:例如LruCache即最近最少使用算法,內(nèi)存容量滿了就回收到現(xiàn)在為止最不常用的。而FifoCache同理,內(nèi)存滿了之后回收最先被緩存的數(shù)據(jù),ScheduledCache則是定時清理緩存了。

7. 二級緩存關(guān)聯(lián)刷新問題

我們直接從一個比較實際的場景出發(fā),首先我們有一張user表,里面有一條id為1的用戶數(shù)據(jù),name是xiaoming,然后有一張關(guān)聯(lián)表money,它記錄xiaoming的錢包金額為50,對應(yīng)數(shù)據(jù)信息如下:

-- SELECT * FROM  `user` u ;

id|name    |
--+--------+
 1|xiaoming|

-- SELECT * FROM money m ;

id|u_id|total|
--+----+-----+
 1|   1|   10|

然后我們在userMapper中寫了這樣一條關(guān)聯(lián)查詢的SQL并開啟二級緩存:

    <select id="selectByUserId" resultType="com.sharkChili.domain.User">
      select u.id id, u.name name , m.total total
      from user u
             inner join money m on u.id = m.u_id
      where u.id = #{id}
    </select>

然后我們執(zhí)行下面這段操作:

  • 通過關(guān)聯(lián)查詢獲取用戶1的姓名和關(guān)聯(lián)表的金額信息。
  • 通過moneyMapper更新用戶1對應(yīng)余額。
  • 通過二級緩再次查詢。

那么問題來了,第二次查詢的金額會是更新后的10嗎?

UserMapper userMapper = SpringUtil.getBean(UserMapper.class);
        User user = new User();
        user.setId(1L);
        //第一次查詢
        User u = userMapper.selectByUserId(user);
        log.info("user:{}", JSONUtil.toJsonStr(u));
        //更新用戶1對應(yīng)的余額信息
        MoneyMapper moneyMapper = SpringUtil.getBean(MoneyMapper.class);
        Money money = new Money();
        money.setId(1L);
        money.setTotal(10L);
        moneyMapper.updateByPrimaryKeySelective(money);

        //第二次查詢
        User u2 = userMapper.selectByUserId(user);
        log.info("user:{}", JSONUtil.toJsonStr(u2));

答案是還是走了臟緩存:

2024-12-13 12:22:09.286 DEBUG 9056 --- [           main] c.s.mapper.UserMapper.selectByUserId     : ==>  Preparing: select u.id id, u.name name , m.total total from user u inner join money m on u.id = m.u_id where u.id = ?
2024-12-13 12:22:09.307 DEBUG 9056 --- [           main] c.s.mapper.UserMapper.selectByUserId     : ==> Parameters: 1(Long)
2024-12-13 12:22:09.325 DEBUG 9056 --- [           main] c.s.mapper.UserMapper.selectByUserId     : <==      Total: 1
2024-12-13 12:22:09.379  INFO 9056 --- [           main] com.sharkChili.WebApplication            : user:{"id":1,"name":"xiaoming","total":50}
2024-12-13 12:22:09.394 DEBUG 9056 --- [           main] c.s.m.M.updateByPrimaryKeySelective      : ==>  Preparing: update money SET total = ? where id = ?
2024-12-13 12:22:09.394 DEBUG 9056 --- [           main] c.s.m.M.updateByPrimaryKeySelective      : ==> Parameters: 10(Long), 1(Long)
2024-12-13 12:22:09.402 DEBUG 9056 --- [           main] c.s.m.M.updateByPrimaryKeySelective      : <==    Updates: 1
2024-12-13 12:22:09.403 DEBUG 9056 --- [           main] com.sharkChili.mapper.UserMapper         : Cache Hit Ratio [com.sharkChili.mapper.UserMapper]: 0.5
2024-12-13 12:22:09.403  INFO 9056 --- [           main] com.sharkChili.WebApplication            : user:{"id":1,"name":"xiaoming","total":50}

原因也很簡單,二級緩存是以namespace為區(qū)域劃分,這意味著userMapper緩存的數(shù)據(jù)不會因為moneyMapper的改變而觸發(fā)更新,這意味著如果涉及關(guān)聯(lián)查詢的緩存數(shù)據(jù)可能會因為關(guān)聯(lián)表的更新無法感知而出現(xiàn)臟緩存:

解決方案也很簡單,我們只要確保緩存更新被關(guān)聯(lián)表時,及時刷新響應(yīng)緩存即可,具體可以參考這篇文章

MyBatis 二級緩存 關(guān)聯(lián)刷新實現(xiàn):https://blog.csdn.net/qq_37217713/article/details/123288123

8. 二級緩存的配置參數(shù)

主要參數(shù)有這么四個:

  • 緩存回收策略(eviction):這個參數(shù)有這么4個LRU最近最少回收算法這種是默認(rèn)的算法、FIFO先進(jìn)先出算法、SOFT算法(基于垃圾回收器算法和軟引用回收的對象)、WEAK算法即基于垃圾回收器算法和弱引用規(guī)則回收對象。
  • 刷新間隔(flushInterval):單位毫秒。
  • 容量(size):引用數(shù)目,正整數(shù)。
  • 是否只讀(readOnly):如果只讀則直接返回緩存實例,性能上會相對有些優(yōu)勢。若不為只讀則會通過序列化獲取對象的拷貝,性能就相對差一些。

配置范例如下所示:

 <cache eviction="FIFO"
           flushInterval="60000"
           size="512"
           readOnly="true"/>

9. 二級緩存的失效場景

有兩種情況一種是第一次查詢的sqlsession沒有提交或者關(guān)閉:

   User1 user1 = user1Mapper.select("1");
        logger.info("二級緩存第一次查詢:[{}]", user1);




        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        User1Mapper user1Mapper1 = sqlSession2.getMapper(User1Mapper.class);
        User1 user13 = user1Mapper1.select("1");
        logger.info("二級緩存第二次查詢:[{}]", user13);

輸出結(jié)果:

2022-11-29 01:05:43,339 [main] DEBUG [com.sharkchili.mapper.User1Mapper.select] - ==>  Preparing: select * from user1 where id = ?
2022-11-29 01:05:43,363 [main] DEBUG [com.sharkchili.mapper.User1Mapper.select] - ==> Parameters: 1(String)
2022-11-29 01:05:43,502 [main] DEBUG [com.sharkchili.mapper.User1Mapper.select] - <==      Total: 1
2022-11-29 01:05:43,506 [main] DEBUG [com.sharkchili.mapper.User1Mapper] - Cache Hit Ratio [com.sharkchili.mapper.User1Mapper]: 0.0
2022-11-29 01:05:43,506 [main] DEBUG [org.apache.ibatis.transaction.jdbc.JdbcTransaction] - Opening JDBC Connection
[main] INFO com.sharkchili.mapper.MyBatisTest - 二級緩存第一次查詢:[User1{id='1', name='小明', user2=null}]
2022-11-29 01:05:44,234 [main] DEBUG [org.apache.ibatis.datasource.pooled.PooledDataSource] - Created connection 550668305.
2022-11-29 01:05:44,234 [main] DEBUG [org.apache.ibatis.transaction.jdbc.JdbcTransaction] - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@20d28811]
2022-11-29 01:05:44,351 [main] DEBUG [com.sharkchili.mapper.User1Mapper.select] - ==>  Preparing: select * from user1 where id = ?
2022-11-29 01:05:44,351 [main] DEBUG [com.sharkchili.mapper.User1Mapper.select] - ==> Parameters: 1(String)
2022-11-29 01:05:44,465 [main] DEBUG [com.sharkchili.mapper.User1Mapper.select] - <==      Total: 1
[main] INFO com.sharkchili.mapper.MyBatisTest - 二級緩存第二次查詢:[User1{id='1', name='小明', user2=null}]

第二種則是常規(guī)更新操作:

2022-11-29 01:07:22,302 [main] DEBUG [com.sharkchili.mapper.User1Mapper.select] - ==>  Preparing: select * from user1 where id = ?
2022-11-29 01:07:22,326 [main] DEBUG [com.sharkchili.mapper.User1Mapper.select] - ==> Parameters: 1(String)
2022-11-29 01:07:22,456 [main] DEBUG [com.sharkchili.mapper.User1Mapper.select] - <==      Total: 1
[main] INFO com.sharkchili.mapper.MyBatisTest - 二級緩存第一次查詢:[User1{id='1', name='小明', user2=null}]
2022-11-29 01:07:22,479 [main] DEBUG [com.sharkchili.mapper.User1Mapper.updatebySet] - ==>  Preparing: update user1 SET id=?, name=? where id=?
2022-11-29 01:07:22,479 [main] DEBUG [com.sharkchili.mapper.User1Mapper.updatebySet] - ==> Parameters: 1(String), aa(String), 1(String)
2022-11-29 01:07:22,713 [main] DEBUG [com.sharkchili.mapper.User1Mapper.updatebySet] - <==    Updates: 1
2022-11-29 01:07:22,714 [main] DEBUG [org.apache.ibatis.transaction.jdbc.JdbcTransaction] - Rolling back JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@f8c1ddd]
2022-11-29 01:07:22,833 [main] DEBUG [org.apache.ibatis.transaction.jdbc.JdbcTransaction] - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@f8c1ddd]
2022-11-29 01:07:22,949 [main] DEBUG [org.apache.ibatis.transaction.jdbc.JdbcTransaction] - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@f8c1ddd]
2022-11-29 01:07:22,949 [main] DEBUG [org.apache.ibatis.datasource.pooled.PooledDataSource] - Returned connection 260840925 to pool.
2022-11-29 01:07:22,949 [main] DEBUG [com.sharkchili.mapper.User1Mapper] - Cache Hit Ratio [com.sharkchili.mapper.User1Mapper]: 0.0
2022-11-29 01:07:22,949 [main] DEBUG [org.apache.ibatis.transaction.jdbc.JdbcTransaction] - Opening JDBC Connection
2022-11-29 01:07:22,949 [main] DEBUG [org.apache.ibatis.datasource.pooled.PooledDataSource] - Checked out connection 260840925 from pool.
2022-11-29 01:07:22,949 [main] DEBUG [org.apache.ibatis.transaction.jdbc.JdbcTransaction] - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@f8c1ddd]
2022-11-29 01:07:23,065 [main] DEBUG [com.sharkchili.mapper.User1Mapper.select] - ==>  Preparing: select * from user1 where id = ?
2022-11-29 01:07:23,065 [main] DEBUG [com.sharkchili.mapper.User1Mapper.select] - ==> Parameters: 1(String)
[main] INFO com.sharkchili.mapper.MyBatisTest - 二級緩存第二次查詢:[User1{id='1', name='小明', user2=null}]
2022-11-29 01:07:23,184 [main] DEBUG [com.sharkchili.mapper.User1Mapper.select] - <==      Total: 1

要想真正用上二級緩存,需要像這樣及時提交或者關(guān)閉其他session:

User1 user1 = user1Mapper.select("1");
        logger.info("二級緩存第一次查詢:[{}]", user1);


        if (sqlSession != null) {
            sqlSession.close();
        }

        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        User1Mapper user1Mapper1 = sqlSession2.getMapper(User1Mapper.class);
        User1 user13 = user1Mapper1.select("1");
        logger.info("二級緩存第二次查詢:[{}]", user13);



        if (sqlSession2 != null) {
            sqlSession2.close();
        }

10. Mybatis一級緩存和二級緩存的區(qū)別

一級緩存默認(rèn)開啟,作用域session,當(dāng)session調(diào)用close或者flush時就會被清空,緩存也是PerpetualCache 一種基于HashMap實現(xiàn)的緩存。 而二級緩存作用于mapper(namespace),也是基于緩存也是PerpetualCache ,默認(rèn)不開啟,需要緩存的屬性類必須實現(xiàn)序列化接口即繼承Serializable,而且二級緩存可以自定義緩存存儲源。

責(zé)任編輯:趙寧寧 來源: 寫代碼的SharkChili
相關(guān)推薦

2024-12-23 06:40:00

2023-06-27 08:37:35

Java反射動態(tài)代理機(jī)制

2010-03-01 17:57:11

WCF緩存機(jī)制

2021-07-22 09:55:28

瀏覽器前端緩存

2023-09-12 14:56:13

MyBatis緩存機(jī)制

2025-02-12 00:29:58

2022-02-15 11:49:08

eBPFGo內(nèi)存

2011-12-22 14:27:11

2013-07-15 11:03:52

802.11ac技術(shù)802.11ac

2015-12-30 14:16:05

iOS動畫視圖渲染

2015-12-23 09:16:33

ios動畫渲染機(jī)制

2009-02-03 14:00:20

PHP運行PHP調(diào)用PHP原理

2022-10-20 18:00:00

MyBatis緩存類型

2009-11-27 10:37:41

GPRS路由

2010-02-04 16:52:01

多層交換技術(shù)

2009-11-12 14:32:00

BGP路由協(xié)議

2009-12-09 10:07:19

Linux靜態(tài)路由

2010-08-04 09:43:28

Flex應(yīng)用程序

2010-11-29 11:22:36

SYBASE數(shù)據(jù)庫日志

2020-07-29 10:10:37

HTTP緩存前端
點贊
收藏

51CTO技術(shù)棧公眾號