mybatis一二级缓存简介

一、前言

1,代码和准备工作见:mybatis工作原理简介 - seeAll - 博客园 (cnblogs.com)

 

二、一级缓存

1,效果展示

1.1,测试代码

代码中,使用SqlSession查询过一次数据;本例在此之后,继续添加一段代码,使用同样的SqlSession再次查询,观察结果数据来自于缓存还是数据库。

 

1.2,观察第一次查询的情况

如下图所示,第一次查询时,缓存为空,从而转向数据库查询数据。同时可知,缓存使用HashMap保存。

 

 

1.3,观察第二次查询的情况

如下图所示,第二次查询时,则直接使用了缓存中的数据库,没有再从数据库中查询了。

 

 

2,作用域

一级缓存的作用域只在SqlSession对象中。

 

2.1,测试代码

当前代码中,使用同一个SqlSession对象查询过两次数据,本例将第二次查询的代码做以下修改,即在第二次查询前将SqlSession对象指向一个新对象。准备执行如下操作:

a,执行第一次查询;

b,修改数据库数据;

c,执行第二次查询;

如果查询结果相同,则说明一级缓存的作用域不止限于SqlSession对象;反之,查询结果不相同,则说明一级缓存的作用域只限于SqlSession对象。

 

2.2,观察第一次查询的情况

和1.2中一样。

 

2.3,修改数据库中数据

 

2.4,观察第二次查询的情况

 

2.5,结果分析

根据以上测试结果,使用SqlSession对象查询数据后,再次使用另外一个SqlSession对象查询相同的数据,并没有从缓存中获取数据,而是从数据库重新查询了数据。可见,一级缓存的作用域仅限于SqlSession对象。

 

2.6,拓展

由以上认知,可以推测,如果一级缓存开启,那么在分布式环境中将会发生如下问题:

a,服务器A查询数据data1后,缓存了数据data1;

b,服务器B修改数据data1为data2;

c,服务器A使用同一个SqlSession对象查询数据,得到的结果为缓存的数据data1,而非当前最新数据data2。

解决办法:

2.6.1,mybatis配置文件设置localCacheScope

在mybatis配置文件中,设置localCacheScope=STATEMENT后,SqlSession每次查询后都会清除缓存。

        <setting name="localCacheScope" value="STATEMENT"/>

测试流程:

a,执行第一次查询;

b,修改数据库数据;

c,执行第二次查询;

结果,两次的查询结果是一样的,不符合预期。

原因:

a,debug了代码,第二次查询时,缓存中确实没有数据,也确实是从数据库查询的;

b,检查了mysql的缓存,只有一个have_query_cache参数,值是no,表示没有缓存;

c,修改数据库数据时,也带上了commit命令,第二次查询的结果还不是修改后的,而是原来的;

d,上网搜索jdbc connection相关问题,偶然发现说mybatis有个参数叫“自动提交”,设置成true和false是有区别的,我设置成true后问题解决。

类似于在sqlyog上按步执行以下步骤:

步骤1:START TRANSACTION;
步骤2:SELECT * FROM u_user;
步骤3:SELECT * FROM u_user;
步骤4:COMMIT;

在步骤2和步骤3之间插入一个操作,使用cmd链接mysql并修改数据库的数据,发现执行步骤3后查到的数据还是老数据。

如果在sqlyog上按步执行以下步骤:

步骤1:START TRANSACTION;
步骤2:SELECT * FROM u_user;
步骤3:COMMIT;
步骤4:START TRANSACTION;
步骤5:SELECT * FROM u_user;
步骤6:COMMIT;

在步骤2后执行操作,使用cmd链接mysql并修改数据库的数据,发现执行步骤5后查到的数据是修改后的数据。

总结下来,当前事务如果执行了查询操作并获得了查询结果,之后再执行相同的查询操作,便无法读取到其他事务已经提交的数据了。如果在步骤1后,让别的事务修改数据,执行步骤2能够读到其他事务提交的数据。

网上看了下,本例的情况属于“可重复读”,即一个事务里(事务开始,还没结束)每次读到的值都是一样的,不管中间其他事务是否修改。也有的时候是“不可重复读”,即一个事务里每次都能读到其他事务已经提交的数据。

-- 设置为:不可重复读
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 设置为:可重复读。(按本例,mysql默认可重复读)
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;

2.6.2,mybatis的一级缓存配置为redis

通过一定的方式(可网上查看),程序员可以让mybatis的一级缓存由原先的HashMap修改为redis,HashMap是服务器jvm私有的,redis是所有服务器共有的。

2.6.3,关闭mybatis缓存,使用redis

和2.6.2不同的是,由程序员来完成缓存的动作,而不是交给mybatis去缓存。比如使用spring-cache,在有些查询方法上面加上@Cacheable,下回查询时就会使用缓存。

 

三、二级缓存

和一级缓存默认开启不同,二级缓存需要手动开启。

 

1,开启二级缓存

1.1,mybatis配置文件修改

在mybatis配置文件中添加如下配置

        <!-- 开启二级缓存 -->
        <setting name="cacheEnabled" value="true"/>

 

1.2,mapper映射文件修改

在mapper.xml映射文件中添加如下配置

    <!-- 使用默认配置,实体对象需要序列化 -->
    <cache></cache>

 

1.3,实体类修改

实体类需要序列化,默认配置时,缓存对象时序列化了;获取缓存时,会反序列化

 

1.4,测试代码修改

a,使用两个不同的SqlSession对象进行查询,从而排除一级缓存的情况,以测试二级缓存。根据上面对一级缓存的测试可知,使用两个不同的SqlSession对象是不走一级缓存的。

b,SqlSession执行查询后需要关闭,否则二级缓存不生效?即使设置“自动提交”为true也无法使二级缓存生效。效果是当SqlSession关闭,“自动提交”不管为true还是false时,二级缓存都能生效。

 

2,效果展示

2.1,观察第一次查询的情况

 

2.2,观察第二次查询的情况

 

3,作用域

3.1,第一次查询

如下图,CacheExecutor中的缓存编号1256,LruCache中的缓存编号1261。

 

 

3.2,第二次查询

如下图,CacheExecutor中的缓存编号1723,LruCache中的缓存编号1261。

 

3.3,总结

  CacheExecutor LruCache
第一次查询 1256 1261
第二次查询 1723 1261

查询时,虽然使用了不同的对象来操作缓存数据,但是数据都是保存到同一个缓存对象(LruCache)中的。

当使用不同的SqlSessionFactory对象时,缓存对象会不一样。

 

 

四、总结

1,一级缓存在同一个SqlSession对象中是有效的;

2,二级缓存在同一个SqlSessionFactory对象中是有效的;

3,一二级缓存都可以指向如redis一样的容器,以应对分布式的情况;

4,一级缓存默认开启,也可以通过配置达到“关闭”的效果;二级缓存默认关闭,需要在多个地方进行配置,以使其生效;

5,mybatis有许多默认配置,如“自动提交”默认为false;二级缓存的默认配置,如清除策略,本例为LRU,还可以配置成FIFO等,如序列化,有些配置下不需要序列化,本例缓存对象序列化取出对象反序列化;

6,分析mybatis的行为时,需要考虑数据库本身的配置;比如本例中,mybatis关闭了一级缓存,前后两次查询之间其他事务执行了更新操作,最终的查询结果仍然一样,让人误以为一级缓存没有真正关闭或者误以为mybatis有隐藏动作,实际是因为数据库设置了事务的隔离级别为“可重复读”,导致一个事务中多次查询结果重复。

posted @ 2024-03-20 16:20  seeAll  阅读(11)  评论(0编辑  收藏  举报