深度解析Mybatis缓存

本文从源码分析Mybatis一级和二级缓存的应用,进而阐述Mybatis缓存的“坑”。

在介绍Mybatis一级缓存和二级缓存之前,需要首先理解两个概念:

  • SqlSession:引用官方文档中对这个接口作用的说明—SqlSession完全包含了面向数据库执行SQL命令所需的所有方法。你可以通过SqlSession实例来直接执行已映射的SQL语句,也可以通过SqlSession得到映射和管理事务。

  • namespace:这里提到的namespace指代的是在应用中配置的mapper配置文件中的namespace。

eg:

1

<mapper namespace=”xxx”></mapper>

下面开始正式介绍Mybatis的一级和二级缓存。

一级缓存:SqlSession维度的缓存,也就是每个SqlSession独享的缓存,我们在使用Mybatis的时候,通常会使用SqlSession的getMapper方法获取到映射。

eg:

1

UserDao userDao = sqlSession.getMapper(UserDao.class);

获取到映射之后执行相关方法进行数据库操作,获取到的映射对象是通过动态代理生成的代理类MapperProxy对象,Mybatis执行SQL的过程不是本文的重点,这里不过多赘述,有兴趣的读者可以自行查阅Mybatis源码。Mybatis的数据库操作是通过Executor来执行,一级缓存也是通过Executor来维护,每个SqlSession都会持有一个Executor:

图中LocalCache就是Mybatis的一级缓存,LocalCache的查询和写入是在Executor内部完成的,BaseExecutor是一个实现了Executor接口的抽象类,通过阅读源码发现,一级缓存LocalCache就是BaseExecutor内部的一个成员变量。

1

2

3

4

public abstract class BaseExecutor implements Executor {

protected PerpetualCache localCache;

public class PerpetualCache implements Cache {

    private Map<Object, Object> cache = HashMap<Object, Object>();

一级缓存保存在PerpetualCache维护的一个Map中,下面为一级缓存执行的流程图:

从流程图中看到,在执行Executor query动作时会尝试从一级缓存中获取缓存数据,下面为相关源码:

在BaseExecutor的query方法中发现了缓存key的构建过程,Mybatis用CacheKey这个对象作为缓存的key,上文中说到一级缓存最终会保存在PerpetualCache维护的一个Map中,我们知道,Map用hashcode和equals来确定对象的唯一性,在CacheKey的update方法中,我们发现了hashcode的运算:

update方法会计算hashcode和checksum并将参数对象保存到了updateList中,而在CacheKey的equals方法中,除去hashcode、checksum和count的比较外,只要updatelist中的元素一一对应相等,那么就可以认为是相等:

所以,只要知道创建Cachekey时updateList中都存放了哪些元素,就可以知道Mybatis是通过哪些因素来确定缓存唯一性,在上文标注的createCacheKey方法中,我们找到了这些元素:

继续分析上文中提到的query方法中调用的重载query方法:

在query方法中发现了清空一级缓存的两处代码,这两处的判断在Mybatis中都是可以配置的。可以在mapper配置文件中配置flushCache来控制数据库操作是否要清空缓存,select默认为false,insert、update、delete默认为true,注意,这个配置一级缓存和二级缓存都会生效,下文会介绍二级缓存。可以使用localCacheScope选项来控制一级缓存的范围,默认为SESSION,会缓存一个SqlSession中的所有查询,如果设置为STATEMENT,则查询会清除缓存。在查询数据库queryFromDatabase方法中,Mybatis做数据库查询操作,并将返回的结果存入一级缓存:

数据库的写操作会清除缓存,同时确认的是insert、update、delete都会统一走update方法:

二级缓存:namespace维度的缓存。上文提到SqlSession中会持有一个Executor,在构建SqlSession的时候,Mybatis会根据cacheEnable选项来确定是否使用一个缓存的Executor,也就是CachingExecutor。cacheEnable可以配置,默认为true:

cacheEnable开启的状态下在mapper配置文件中配置可以控制缓存策略,介绍一级缓存的时候我们提到了可以使用flushCache选项来控制是否清空缓存,这个配置同样会作用于二级缓存。mybatis在解析配置文件的时候,会将配置的参数解析成Cache对象保存到MappedStatement(可以理解为mapper配置文件的java代码实现,对象中保存了在mapper配置文件中配置的选项)中,解析配置文件的过程不是本文的内容,感兴趣的读者可以自行查看Mybatis源码。构建好SqlSession后,数据库操作就会委托给CachingExcutor进行执行:

这是一个典型的按需加载缓存的方式,注意,tcm.putObject方法执行完之后缓存并没有真正的生效,这里只是记录了这次查询将要产生缓存变更,这时候相同的sql查询缓存是不会生效的。同样的,写操作也不是马上会清除缓存:

在执行SqlSession的commit方法之后,缓存的变更会真正的被刷新到缓存中去,开始真正的发挥作用:

我们看到在保存缓存和刷新缓存时用到的delegate对象就是上文中提到的构造TransactionalCache对象时传入的Cache对象了,在构建Cache对象时,Mybatis采用装饰者模式为Cache装饰了不同的功能,有兴趣的读者可以阅读相关源码来了解这部分的内容。缓存保存在PerpetualCache中:

到这里,整个Mybatis缓存的分析就结束了,最后我们分析一下Mybatis缓存到底有什么“坑”。在介绍一级缓存时我们提到Mybatis的一级缓存是SqlSession级别的缓存,不同的SqlSession之间缓存是不共享的,如果两个SqlSession操作同一张表,这时候就可能出现其中一个SqlSession获取到的数据是过期的,我们在使用这个SqlSession查询就有可能读取过期的脏数据。在介绍二级缓存时我们提到二级缓存是namespace维度的缓存,全局共享整个namespace的缓存,当我们把针对同一张表的sql操作写到两个不同的mapper文件中或者使用表关联查询时,就很容易出现两个mapper中查询出来的同一条数据不一致的情况。所以在实际应用中,我们建议将cacheEnable设置为false、localCacheScope设置为STATEMENT,不使用mybatis的缓存,需要缓存的时候用应用程序中的缓存来控制来避免Mybatis的缓存坑。

转载请并标注: “本文转载自 linkedkeeper.com (文/张强)”  ©著作权归作者所有


Page 2

阅读数


Page 3

阅读数


Page 4

阅读数


Page 5

阅读数


Page 6

阅读数


Page 7

阅读数


Page 8

阅读数


Page 9

阅读数


Page 10

阅读数


Page 11

阅读数


Page 12

阅读数


Page 13

阅读数


Page 14

阅读数


Page 15

阅读数


Page 16

阅读数


Page 17

阅读数


Page 18

阅读数


Page 19

阅读数


Page 20

阅读数


Page 21

阅读数


Page 22

阅读数


Page 23

阅读数


Page 24

阅读数


Page 25

阅读数


Page 26

阅读数

首页 - Wiki
Copyright © 2011-2025 iteam. Current version is 2.148.1. UTC+08:00, 2025-11-24 02:54
浙ICP备14020137号-1 $访客地图$