为什么Mybatis一级和二级缓存都不建议使用?(三)
测试二级缓存
二级缓存是基于namespace实现的,即一个mapper映射文件用一个缓存
在本实验中,id为1的学生名称初始化为点点。
「实验1」
测试二级缓存效果,不提交事务,sqlSession1查询完数据后,sqlSession2相同的查询是否会从缓存中获取数据。
执行结果:
我们可以看到,当sqlsession没有调用commit()方法时,二级缓存并没有起到作用。
「实验2」
测试二级缓存效果,当提交事务时,sqlSession1查询完数据后,sqlSession2相同的查询是否会从缓存中获取数据。
从图上可知,sqlsession2的查询,使用了缓存,缓存的命中率是0.5。
「实验3」
测试update操作是否会刷新该namespace下的二级缓存。
我们可以看到,在sqlSession3更新数据库,并提交事务后,sqlsession2的StudentMapper namespace下的查询走了数据库,没有走Cache。
「实验4」
验证MyBatis的二级缓存不适应用于映射文件中存在多表查询的情况。
getStudentByIdWithClassInfo的定义如下
通常我们会为每个单表创建单独的映射文件,由于MyBatis的二级缓存是基于namespace的,多表查询语句所在的namspace无法感应到其他namespace中的语句对多表查询中涉及的表进行的修改,引发脏数据问题。
执行结果:
在这个实验中,我们引入了两张新的表,一张class,一张classroom。class中保存了班级的id和班级名,classroom中保存了班级id和学生id。我们在StudentMapper中增加了一个查询方法getStudentByIdWithClassInfo,用于查询学生所在的班级,涉及到多表查询。在ClassMapper中添加了updateClassName,根据班级id更新班级名的操作。
当sqlsession1的studentmapper查询数据后,二级缓存生效。保存在StudentMapper的namespace下的cache中。当sqlSession3的classMapper的updateClassName方法对class表进行更新时,updateClassName不属于StudentMapper的namespace,所以StudentMapper下的cache没有感应到变化,没有刷新缓存。当StudentMapper中同样的查询再次发起时,从缓存中读取了脏数据。
「实验5」
为了解决实验4的问题呢,可以使用Cache ref,让ClassMapper引用StudenMapper命名空间,这样两个映射文件对应的SQL操作都使用的是同一块缓存了。
mapper文件中的配置如下
<cache-ref namespace="mapper.StudentMapper"/>
执行结果:
不过这样做的后果是,缓存的粒度变粗了,多个Mapper namespace下的所有操作都会对缓存使用造成影响。
总结
mybatis的一级缓存和二级缓存都是基于本地的,分布式环境下必然会出现脏读。
二级缓存可以通过实现Cache接口,来集中管理缓存,避免脏读,但是有一定的开发成本,并且在多表查询时,使用不当极有可能会出现脏数据
「除非对性能要求特别高,否则一级缓存和二级缓存都不建议使用」
文章转自公众号:Java识堂