mybatis缓存知多少

本文详细介绍了MyBatis的一级缓存和二级缓存工作原理及应用场景。一级缓存默认开启,基于SqlSession,不可关闭,而二级缓存基于Mapper的namespace,可跨SqlSession共享。通过配置开启二级缓存,并展示了如何在实际操作中使用。文章还讨论了如何避免脏读问题,以及如何通过设置flushCache属性来管理缓存刷新。
摘要由CSDN通过智能技术生成

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();
  }


控制台打印:
在这里插入图片描述
对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();
  }

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;

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

(1)Mapper的Id,即Mappper命名空间与<select|update|insert|delete>标签的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文件中加入以下代码:

<settings>
    <setting name="cachaEnabled" value="true"/>
  </settings>

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

<cache></cache>

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("===========第二次查询结束=============");

 }

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

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

<select id="findUserById" resultMap="userMap" useCache="false">
    select * from t_user where id =#{id}
  </select>

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

 <select id="findUserById" resultMap="userMap" useCache="false"  flushCache="true">
    select * from t_user where id =#{id}
  </select>

一般下执行完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对象的关键代码:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

弯_弯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值