mybatis|mybatis一级缓存和二级缓存原理

前言:

首先来说下缓存的出现相信大家也都知道,为了提高性能减少对数据库的压力,从而提高响应速度。缓存的数据大多都是存于内存当中,所以查询的响应速度非常快的,这就是缓存的存在的一个作用。其中mybatis也为我们提供了相对应的缓存实现存在。
一级缓存的会话为SESSION和STATEMENT两种,默认是SESSION。缓存范围为namespace级别,二级缓存是mybatis默认会给我们开启的,是基于我们的会话来实现的,查询必须是在同一个会话下才会共享缓存区域。其实在我的理解为一级缓存也可以被认为是本地缓存二级为自定义缓存。虽然mybatis为我们提供了响应的实现。我们也可以进行实现相应的Cache接口。接下来就从我们的源码分析角度去看怎么执行相应的执行流程。
1.构建执行器
这里为什么说要构建我们的执行器,因为该执行器和我们的缓存都是息息相关的,所有查询都是经过执行器进行一个执行操作的。这里我简单的介绍下关于执行器的介绍,帮助大家更有效的了解缓存的一个执行过程,这里和执行器的创建都是有关联的。
执行器接口:Executor.class该接口是mybatis为我们提供的执行器接口,具体的执行器实现分为三种。结构图如下:
mybatis|mybatis一级缓存和二级缓存原理
文章图片

BatchExecutor:批处理执行器,用于将多个SQL一次性输出到数据库。 ReuseExecutor:可重用的执行器,重用的对象是Statement,也就是说该执行器会缓存同一个sql的Statement,省去Statement的重新创建,优化性能。 SimpleExecutor:简单的执行器,默认会创建该执行器。

具体的实现可以分为上述的三大执行器。这里其实使用到了一个设计模式,为模板设计模式,公共的模板接口信息都被定义在BaseExecutor.class模板执行器当中,由以上三个执行器都进行一个继承,从而公共的方法都可以调用BaseExecutor.class里面的,其中就最为重要的就包括一个一级缓存的实现,后面会以源码做为分析,这就是mybatis为我们提供的默认缓存实现。对于Executor.class执行器的实现还有一实现CachingExecutor.class的实现,从该名称不难看出这是一个带有缓存的执行器,这个执行器就是二级缓存的一个体现,这里使用了一个我个人理解的一个装饰模式。具体我在下面源码分析中会进行阐述。
2.执行器构建过程
这里从我们的案例开始,关键点在于构建SqlSession的这个过程。
public static SqlSession getSqlSession() throws IOException { //获得核心文件配置 InputStream resourceAsStream = Resources.getResourceAsStream("sqlUserMapper.xml"); //获取session工厂对象,默认创建的是DefaultSqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //获得session会话对象,这里默认创建的是DefaultSqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); return sqlSession; }

sqlSessionFactory.openSession(); 执行该方法我们看看方法的内部一个实现。
@Override public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); }/** * 这里就是创建相应的一个会话,默认是DefaultSqlSession * @param execType 创建执行器的类型 * @param level 对应的事务管理器 * @param autoCommit 是否默认提交事务,这里默认是不提交的 * @return */ private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { //拿到环境配置,里面包含主要包含着数据源信息 final Environment environment = configuration.getEnvironment(); //获取事务工厂,这里使用的是JdbcTransactionFactory final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); //这里默认创建的是JdbcTransaction,创建事务 tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); //根据事务和执行器类别创建具体的执行器,这里使用了简单工厂模式给我们创建具体的执行器对象 final Executor executor = configuration.newExecutor(tx, execType); return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session.Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }

这一步关键的一步Executor executor = configuration.newExecutor(tx, execType); 该方法就是在帮我们构建相应的一个执行器。使用了简单工厂模式进行为我们创建具体的执行器。
mybatis|mybatis一级缓存和二级缓存原理
文章图片

这里我们可以关注executor = new CachingExecutor(executor)一个带有缓存的执行器的创建过程,我们可以看CachingExecutor.class的构造器,后面在执行的时候我们就会感觉不会那么陌生。
mybatis|mybatis一级缓存和二级缓存原理
文章图片

到了这一步为我们创建了相应的执行器,其实大家也好奇为什么一个一级缓存和二级缓存我为什么在这里讲解一个执行器的创建过程,其实我们要了解我们的查询是如何执行的都是该组件在帮我们进行一个具体的操作,所以讲解该流程是为了大家更好的了解一个缓存查找的一个流程。在后续的查询缓存中能更好的了解其中的原理。
上述在构建Executor.class执行器的时候就是在为我们创建会话的时候进行创建的。具体的查询过程我们这里没有详细的讲解,这里主要关注一个缓存的原理。需要了解为什么能快速到达这一步查询可以进行观看我的mybatis插件执行原理,后续讲持续更新执行流程:
https://blog.csdn.net/m0_49516995/article/details/118273675?spm=1001.2014.3001.5501
mybatis|mybatis一级缓存和二级缓存原理
文章图片

执行相应的查询,这里就需要我们的执行器进行一个查询操作了。
mybatis|mybatis一级缓存和二级缓存原理
文章图片

使用一个带有缓存的执行器,为我们执行一个查询操作。看源码分析:
@Override public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { //获取一级缓存,对于一级缓存我们都是可以自定义实现的。 Cache cache = ms.getCache(); //这里如果我们开启了一个缓存的话就会进行一个缓存的查询的操作 if (cache != null) { flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, boundSql); //从缓存获取数据 @SuppressWarnings("unchecked") List list = (List) tcm.getObject(cache, key); if (list == null) { //这里内部为我们维护者一个具体的执行器,当从缓存中没有获取到数据就会使用具体的执行器进行一个查询 list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); //加入缓存 tcm.putObject(cache, key, list); } return list; } } //使用具体的执行器为我们提供一个查询操作,这里会跳到BaseExecutor.class为我们执行一个二级缓存的查询 return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }

说到这里其实第一次看缓存的一个执行流程的各位估计会比较绕,这里以我的该案例为演示,查询会经历的执行器跳转,相信大家在执行一个查询的会感觉会在几个执行器之前跳来跳去,这就是我在上面会为什么在写缓存原理的时候讲解了一下Executor执行器的一个创建。
上述我们如果没有从二级缓存中获取到数据,就会委托我们具体的执行器为我们执行查询操作。这里会由BaseExecutor.class为我们执行相应的一级缓存,该缓存处于会话级别的,为什么会在BaseExecutor.class进行一个查询呢,我们可以从上面看到该类的结构,采用了一个模板设计模式,一些共同的查询操作都定义在该类里面,其中就有一级缓存的定义。如果在模板里面执行查询,就是我们的一级缓存查询没有获取到结果集,就会让子类进行一个查询操作的具体实现。这里希望大家都已经熟悉了模板设计模式。
@Override public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ......省略 List list; try { queryStack++; //从缓存获取结果集 list = resultHandler == null ? (List) localCache.getObject(key) : null; if (list != null) { handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { //从数据库执行查询 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } ......省略 return list; }

如果在一级缓存没有查询到结果集,就会到我们具体的执行器,上面我们讲到了我们有三种执行器的具体实现,可以回顾上述。从数据库进行查询调用queryFromDatabase()方法
mybatis|mybatis一级缓存和二级缓存原理
文章图片

到了这里我们的缓存原理已经讲解完毕,大家在看的途中获取会因为在执行的时候在几个执行器之间跳来跳去,希望本篇文章能给大家更好的理解mybatis的缓存带来帮助。本文章有什么讲解的不对的地方希望各位能提出意见,我们共同努力。以上是我在读源码当中的一个个人理解,有错误的地方能指点一下。


【mybatis|mybatis一级缓存和二级缓存原理】


    推荐阅读