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有隐藏动作,实际是因为数据库设置了事务的隔离级别为“可重复读”,导致一个事务中多次查询结果重复。