• mybatis缓存知多少


    mybatis作为强大的持久层框架,缓存是必不可少的。Mybatis的缓存分为一级缓存和二级缓存,在本质上是相同的,它们都是用的Cache接口实现。

    一级缓存

    一级缓存默认是开启,并且是不可以关闭的,原因是因为Mybatis的一些关键特性都是基于mybatis一级缓存实现的,例如: h和建立级联映射、避免循环引用等。而且mybatis结果集映射是高度依赖CacheKey的。
    首先看下一级缓存的应用,在一个SqlSession 中,对User表根据id进行两次查询.

    @Test
      public  void findUserById() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory =new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession=sqlSessionFactory.openSession();
    
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        System.out.println("===========第一次查询开始=============");
        User user1 = userMapper.findUserById(1);
        System.out.println(user1);
        System.out.println("===========第一次查询结束=============");
    
    
        System.out.println("===========第二次查询开始=============");
        User user2 = userMapper.findUserById(1);
        System.out.println(user2);
        System.out.println("===========第二次查询结束=============");
        sqlSession.close();
      }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    控制台打印:
    在这里插入图片描述
    对user进行查询后,再进行update操作:

    @Test
      public  void updateUser2() throws IOException {
        //加载配置文件
        InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
    
        //加载sqlsessionFactroy对象
        SqlSessionFactory sqlSessionFactory =new SqlSessionFactoryBuilder().build(resourceAsStream);
    
        //加载sqlSession对象
        SqlSession sqlSession =sqlSessionFactory.openSession();
    
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        System.out.println("===========第一次查询开始=============");
        User user1 = userMapper.findUserById(1);
        System.out.println(user1);
        user1.setUsername("豆豆");
        user1.setPassword("qwe123");
        user1.setId(1);
        sqlSession.update("user.updateUser",user1);
        //提交事务
        sqlSession.commit();
        System.out.println("===========第一次查询结束=============");
    
    
        System.out.println("===========第二次查询开始=============");
        User user2 = userMapper.findUserById(1);
        System.out.println(user2);
        user2.setUsername("豆豆1");
        user2.setPassword("qwer1234");
        user2.setId(1);
         sqlSession.update("user.updateUser",user1);
        //提交事务
        sqlSession.commit();
        System.out.println("===========第二次查询结束=============");
        
    
        //释放资源
        sqlSession.close();
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    image

    总结

    1、第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从 数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。

    2、 如果中间sqlSession去执行commit操作(执行插入、更新、删除),则会清空SqlSession中的 一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。

    3、 第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直 接从缓存中获取用户信息

    一级缓存原理

    Mybatis 的缓存基于jvm堆实现的, 所有的缓存数据都放在java对象中,通过Cache解控定义缓存对象的行为:
    在这里插入图片描述
    Mybatis 的一级缓存是SqlSession级别的缓存,使用PerpetualCache 实例实现。在BaseExecutor类中定义了两个PerpetualCache属性,如下:

      // Mybatis 一级缓存对象,用于缓存Mybatis 查询结果
      protected PerpetualCache localCache;
      //存储过程输出参数缓存,用于缓存存储过程调用结果
      protected PerpetualCache localOutputParameterCache;
    
    • 1
    • 2
    • 3
    • 4

    这两个属性由BaseExecutor构造方法进行初始化
    在这里插入图片描述
    在进行查询操作时,会先创建CacheKey对象。如果两次查询操作CacheKey对象相同,就会被认为这两次查询执行的是相同的SQL语句。 通过BaseExecutor.createCacheKey()方法创建CacheKey对象,代码如下:
    在这里插入图片描述
    在代码中可知,影响缓存的key的因素有以下4个:

    (1)Mapper的Id,即Mappper命名空间与标签的id组成的全局限定名。

    (2)查询结果的偏移量及查询的条数。

    (3)具体的SQL语句及SQL语句中需要传递的所有参数。

    (4)Mybatis 主配置文件中,通过标签配置的环境信息对应的id属性值。

    执行两次查询时,需要4个因素完全相同,才会认为两次查询执行的是相同的SQL语句,缓存才会失效。

    在BaseExecutor类的query()方法职工,根据缓存key从localCache属性中查找 是否有缓存对象,如果查询不到,则调用queryFromDatabase()方法从数据库中获取数据,然后将数据写入localCache对象中。代码如下:
    在这里插入图片描述
    在这里插入图片描述
    值得注意的是,如果localCacheScope 属性设置为STATEMENT,则每次查询操作完成后,都会调用clearLocalCache()方法清空缓存。在分布式环境下,必须将localCacheScope 属性设置为STATEMENT,避免其他应用节点执行SQL更新语句后,本节点缓存得不到刷新而导致的数据一致性问题。

    二级缓存

    二级缓存的原理和一级缓存原理一样,第一次查询,会将数据放入缓存中,然后第二次查询则会直接去缓存中取。但是一级缓存是基于sqlSession的,而二级缓存是基于mapper文件的namespace的,也就是说多个sqlSession可以共享一个mapper中的二级缓存区域,并且如果两个mapper的namespace相同,即使是两个mapper,那么这两个mapper中执行sql查询到的数据也将存在相同的二级缓存区域中.

    开启二级缓存
    一级缓存默认是开启状态的, 但是二级缓存是需要手动开启,

    首先在全局配置环境文件sqlMapConfig.xml文件中加入以下代码:

    
        
      
    
    • 1
    • 2
    • 3

    然后在相关的xxxMapper.xml文件中开启缓存

    
    
    • 1

    mapper.xml文件中就这么一个空标签,其实这里可以配置,PerpetualCache这个类是mybatis默认实现缓存功能的类。我们不写type就使用mybatis默认的缓存,也可以去实现Cache接口 来自定义缓存。测试二级缓存和sqlSession无关

    @Test
     public  void testTwoCache() throws IOException {
       //加载外部资源
       InputStream resoureAsStream = Resources.getResourceAsStream("mybatis-config.xml");
    
       //获取sqlSessionFactory对象
       SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resoureAsStream);
    
       //获取SqlSession对象
       SqlSession sqlSession1 =sqlSessionFactory.openSession();
       SqlSession sqlSession2 =sqlSessionFactory.openSession();
    
       UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
       UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
    
       System.out.println("===========第一次查询开始=============");
       User user1 = userMapper1.findUserById(1);
       System.out.println(user1);
       sqlSession1.close();
       System.out.println("===========第一次查询结束=============");
    
    
       System.out.println("===========第二次查询开始=============");
       User user2 = userMapper2.findUserById(1);
       System.out.println(user2);
       sqlSession2.close();
       System.out.println("===========第二次查询结束=============");
    
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    可以看出上面两个不同的sqlSession,第一个关闭了,第二次查询依然不发出sql查询语句:

    mybatis中还可以配置userCache和flushCache等配置项,userCache是用来设置是否禁用二级缓存的,在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询,默认情况是true,即该sql使用二级缓

    
    
    • 1
    • 2
    • 3

    这种情况是针对每次查询都需要最新的数据sql,要设置成useCache=false,禁用二级缓存,直接从数 据库中获取。
    在mapper的同一个namespace中,如果有其它insert、update, delete操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读。
    设置statement配置中的flushCache="true”属性,默认情况下为true,即刷新缓存,如果改成false则 不
    会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。

     
    
    • 1
    • 2
    • 3

    一般下执行完commit操作都需要刷新缓存,flushCache=true表示刷新缓存,这样可以避免数据库脏读。所以我们不用设置,默认即可.

    二级缓存实现原理

    如果CacheEnable 属性值为true,则使用CachingExecutor 对Executor对象进行装饰增加二级缓存功能。CachingExecutor 类中维护TransactionalCacheManager 用于管理所有的二级缓存对象。在TransactionalCacheManager类中通过一个HashMap对象维护所有二级缓存实例对应的TransactionalCache对象。getObject()方法和putObject()方法中都会调用getTransactionalCache()方法获取二级缓存对象对应的TransactionalCache对象,然后对TransactionalCache对象进行操作。在getTransactionalCache()方法中,首先从HashMap对象中获取二级缓存对象对应的TransactionalCache对象,如果获取不到,则创建新的TransactionalCache对象添加到HashMap对象中。

    在CachingExecutor的query()方法中,首先调用createCacheKey()方法创建缓存Key对象,然后调用MappedStatement对象的getCache()方法获取MappedStatement对象中维护的二级缓存对象。然后尝试从二级缓存对象中获取结果,如果获取不到,则调用目标Executor对象的query()方法从数据库获取数据,再将数据添加到二级缓存中。当执行更新语句后,同一命名空间下的二级缓存将会被清空。代码如下:
    在这里插入图片描述

    在flushCachelfRequired()方法中会判断标签的flushCache属性,如果属性值为true,就清空缓存。标签的flushCache属性值默认为false,而标签的flushCache属性值默认为true.以update()方法为例,代码如下:
    在这里插入图片描述
    在这里插入图片描述
    在XMLMapperBuilder在解析Mapper配置时会调用cacheRefElement()方法解析标签,代码如下:
    在这里插入图片描述

    在这里插入图片描述
    在获取标签的所有属性信息后,调用MapperBuilderAssistant对象的userNewCache()方法创建二级缓存实例,然后通过MapperBuilderAssistant的currentCache属性保存二级缓存对象的引用。在调用MapperBuilderAssistant对象的addMappedStatement()方法创建MappedStatement对象时会将当前命名空间对应的二级缓存对象的引用添加到MappedStatement对象中。下面是创建MappedStatement对象的关键代码:
    在这里插入图片描述

  • 相关阅读:
    《模型结构图绘制 -- Axure 软件使用教程》学习笔记
    MySQL命令行基本命令
    linux任务优先级
    JDBC编程
    windows 单机 - elasticsearch-7.11.1 、kibana-7.11.1 安装部署
    ACL原理与配置(一、前言 二、ACL概述 三、ACL的组成实验演示通配符(1)通配符(2) ACL的分类与标识ACL基本配置 ACL高级配置​编辑)
    Open3D 点云最小二乘法拟合平面
    python科研绘图:绘制X-bar图
    人工智能数学基础--概率与统计14:连续随机变量的指数分布、威布尔分布和均匀分布
    统计能整除数字的位数
  • 原文地址:https://blog.csdn.net/tang_huan_11/article/details/125446221