<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      由 Mybatis 源碼暢談軟件設計(七):從根上理解 Mybatis 一級緩存

      本篇我們來講 一級緩存,重點關注它的實現原理:何時生效、生效范圍和何時失效,在未來設計緩存使用時,提供一些借鑒和參考。

      1. 準備工作

      定義實體

      public class Department {
      
          public Department(String id) {
              this.id = id;
          }
      
          private String id;
      
          /**
           * 部門名稱
           */
          private String name;
      
          /**
           * 部門電話
           */
          private String tel;
      
          /**
           * 部門成員
           */
          private Set<User> users;
      }
      
      public class User {
      
          private String id;
      
          private String name;
      
          private Integer age;
      
          private LocalDateTime birthday;
      
          private Department department;
      }
      

      定義 Mapper.xml

      DepartmentMapper.xml,兩條 SQL:一條根據 ID 查詢;一條清除緩存,標記了 fulshCache 標簽,將其設置為 true 后,只要語句被調用,都會將本地緩存和二級緩存清空(默認值為 false)

      <select id="findById" resultType="Department">
          select * from department
          where id = #{id}
      </select>
      
      <select id="cleanCathe" resultType="int" flushCache="true">
          select count(department.id) from department;
      </select>
      

      UserMapper.xml,聯表查詢用戶信息:

      <select id="findAll" resultMap="userMap">
          select u.*, td.id, td.name as department_name
          from user u
          left join department td
          on u.department_id = td.id
      </select>
      

      2. 一級緩存

      一級緩存的生效范圍 SqlSession 級別的,不同 SqlSession 間不共享緩存,它默認情況下是啟用的。主要作用是減少在同一個查詢 SQL 會話中對數據庫的重復查詢,從而提高性能。以如下用例為例:

          public static void main(String[] args) throws IOException {
              InputStream xml = Resources.getResourceAsStream("mybatis-config.xml");
              SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
              // 開啟二級緩存需要在同一個SqlSessionFactory下,二級緩存存在于 SqlSessionFactory 生命周期,如此才能命中二級緩存
              SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(xml);
      
              SqlSession sqlSession = sqlSessionFactory.openSession();
              DepartmentMapper departmentMapper = sqlSession.getMapper(DepartmentMapper.class);
              System.out.println("----------department第一次查詢 ↓------------");
              departmentMapper.findById("18ec781fbefd727923b0d35740b177ab");
              System.out.println("----------department一級緩存生效,控制臺看不見SQL ↓------------");
              departmentMapper.findById("18ec781fbefd727923b0d35740b177ab");
      
          }
      

      可以發現在第二次查詢時,一級緩存生效,控制臺沒有出現SQL:

      image.png

      而我們清空下一級緩存再試試:

          public static void main(String[] args) throws IOException {
              InputStream xml = Resources.getResourceAsStream("mybatis-config.xml");
              SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
              // 開啟二級緩存需要在同一個SqlSessionFactory下,二級緩存存在于 SqlSessionFactory 生命周期,如此才能命中二級緩存
              SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(xml);
      
              SqlSession sqlSession = sqlSessionFactory.openSession();
              DepartmentMapper departmentMapper = sqlSession.getMapper(DepartmentMapper.class);
              System.out.println("----------department第一次查詢 ↓------------");
              departmentMapper.findById("18ec781fbefd727923b0d35740b177ab");
              System.out.println("----------department一級緩存生效,控制臺看不見SQL ↓------------");
              departmentMapper.findById("18ec781fbefd727923b0d35740b177ab");
              System.out.println("----------清除一級緩存 ↓------------");
              departmentMapper.cleanCathe();
              System.out.println("----------清除后department再一次查詢,SQL再次出現 ↓------------");
              departmentMapper.findById("18ec781fbefd727923b0d35740b177ab");
          }
      

      控制臺日志很清晰,清除緩存后又重新查了一遍:

      image.png

      接下來我們看一下不同 SqlSession 間一級緩存是否共享,創建一個新的 SqlSession sqlSession1 執行相同的SQL:

          public static void main(String[] args) throws IOException {
              SqlSession sqlSession = sqlSessionFactory.openSession();
              SqlSession sqlSession1 = sqlSessionFactory.openSession();
              DepartmentMapper departmentMapper = sqlSession.getMapper(DepartmentMapper.class);
              DepartmentMapper departmentMapper1 = sqlSession1.getMapper(DepartmentMapper.class);
              System.out.println("----------department第一次查詢 ↓------------");
              departmentMapper.findById("18ec781fbefd727923b0d35740b177ab");
              System.out.println("----------sqlSession1下department執行相同的SQL,控制臺出現SQL ↓------------");
              departmentMapper1.findById("18ec781fbefd727923b0d35740b177ab");
          }
      

      如控制臺日志所示,可以發現在不同的 SqlSession 下不共享一級緩存:

      image.png

      3. 一級緩存原理

      一級緩存在查詢方法 org.apache.ibatis.executor.BaseExecutor#query 中生效,如下所示:

      public abstract class BaseExecutor implements Executor {
          // ...
          
          // 一級緩存
          protected PerpetualCache localCache;
      
          public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
                                   CacheKey key, BoundSql boundSql) throws SQLException {
              ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
              if (closed) {
                  throw new ExecutorException("Executor was closed.");
              }
              // 判斷是否刷新本地緩存
              if (queryStack == 0 && ms.isFlushCacheRequired()) {
                  clearLocalCache();
              }
              List<E> list;
              try {
                  queryStack++;
                  // 判斷一級緩存是否存在,存在則直接作為結果返回,否則查詢數據庫
                  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 #601
                  deferredLoads.clear();
                  if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                      // issue #482
                      clearLocalCache();
                  }
              }
              return list;
          }
      
          private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds,
                                                ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
              List<E> list;
              // 一級緩存占位
              localCache.putObject(key, EXECUTION_PLACEHOLDER);
              try {
                  list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
              } finally {
                  // 查詢完成后清除一級緩存
                  localCache.removeObject(key);
              }
              // 添加到一級緩存中
              localCache.putObject(key, list);
              // 存儲過程相關邏輯
              if (ms.getStatementType() == StatementType.CALLABLE) {
                  localOutputParameterCache.putObject(key, parameter);
              }
              return list;
          }
      }
      

      其中 PerpetualCache localCache 便是一級緩存,它的實現借助了 HashMap

      public class PerpetualCache implements Cache {
      
          private final String id;
      
          private final Map<Object, Object> cache = new HashMap<>();
       
          // ...
      }
      

      一級緩存生效的邏輯也非常簡單,如下所示:

      一級緩存.drawio.png

      queryFromDatabase 方法中有一段蠻有意思的邏輯 localCache.putObject(key, EXECUTION_PLACEHOLDER);:在添加一級緩存前會先添加緩存 占位符 EXECUTION_PLACEHOLDER,但是這個占位符并沒有被用作一個明確的同步機制來阻止其他線程的查詢執行,所以它只是標記一個查詢正在進行,提供了 防止在同一事務上下文中重復執行相同的查詢的 基礎,這種設計可能是 MyBatis 開發者認為在多數情況下,數據庫查詢的開銷相對較小或同一事務中幾乎不執行多次相同的查詢,而不是為了在多線程環境下保證不擊穿數據庫,降低數據庫的壓力。

      這種設計模式在分布式緩存系統中很常見,一般用于 解決 ”緩存擊穿“ 問題,幫助系統在高并發環境下保持穩定性。

      一級緩存失效場景

      • 兩次相同查詢SQL間有 Insert、Delete、Update 語句執行時:Insert、Delete、UpdateflushCache標簽 默認為 true ,執行它們時,會將一級緩存清空

      • 調用 sqlSession#clearCache 方法

      • SqlSession 被關閉時,一級緩存也會被清空

      緩存的是對象的引用

      以如下代碼為例,第一次查詢結果中 name 字段的值為 null,將其賦值再進行第二次查詢:

              System.out.println("----------department第一次查詢 ↓------------");
              Department department = departmentMapper.findById("18ec781fbefd727923b0d35740b177ab");
              System.out.println(department);
              department.setName("   把名字改了");
      
              System.out.println("----------department一級緩存生效,控制臺看不見SQL ↓------------");
              System.out.println(departmentMapper.findById("18ec781fbefd727923b0d35740b177ab"));
      

      可以發現第二次查詢取緩存的結果是 更改name結果之后的

      11111.png

      這是因為一級緩存中 存放的數據其實是對象的引用,導致第二次從一級緩存中查詢到的數據,就是我們剛剛改過的數據,而并不是數據庫中真實的數據。在同一個 SqlSession 中,如果對緩存中返回的對象進行了修改,而沒有同步更新數據庫,那么在后續的查詢中會返回被修改的對象,而不是數據庫中的最新數據,導致臟讀。

      4. 總結

      • 一級緩存基于 SqlSession,不同 SqlSession 間不共享一級緩存

      • 一級緩存被保存在 BaseExecutorPerpetualCache 中,本質上是 HashMap

      • 執行 Insert、Delete、Update 語句會使一級緩存失效

      • 一級緩存存放的數據是對象的引用,若對它進行修改,則之后取出的緩存為修改后的數據

      巨人的肩膀

      posted @ 2025-10-29 20:24  京東云開發者  閱讀(4)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 少妇高潮太爽了在线视频| av一本久道久久波多野结衣| 亚洲午夜无码久久久久小说| 亚洲国产综合一区二区精品| 无码人妻丰满熟妇区bbbbxxxx| 69天堂人成无码免费视频| 国产最大成人亚洲精品| 日韩精品一区二区三区激情| 日韩一区二区三区精品区| 欧美日韩在线第一页免费观看| 亚洲最大成人av在线天堂网| 亚洲的天堂在线中文字幕| 涟水县| 国产日韩综合av在线| ww污污污网站在线看com| 免费人成视频在线 | 国产中文字幕精品视频| 精品国产乱码一区二区三区| 午夜射精日本三级| 精品免费看国产一区二区| 午夜在线观看成人av| 亚洲 一区二区 在线| 色爱综合激情五月激情| 精品国产片一区二区三区| 狠狠色丁香婷婷综合尤物| 激情综合网一区二区三区| 麻豆亚洲精品一区二区| 二区三区亚洲精品国产| 国产视频最新| 四虎影视库国产精品一区| 亚在线观看免费视频入口| 午夜毛片不卡免费观看视频| 国产精品不卡一二三区| 欧美黑人XXXX性高清版| 在线精品国产中文字幕| 99热精品久久只有精品| 天堂网亚洲综合在线| 国产在线一区二区在线视频| 国产高清视频一区二区三区 | 91人妻熟妇在线视频| 欧美日韩国产图片区一区|