Skip to main content

MyBatis

缓存

MyBatis 跟缓存相关的类都在 cache 包里面,其中有一个 Cache 接口,只有一个默认的实现类 PerpetualCache,它是用 HashMap 实现的。

一级缓存

一级缓存也叫本地缓存,MyBatis 的一级缓存是在会话(SqlSession)层面进行缓存的。MyBatis 的一级缓存是默认开启的,不需要任何的配置。 同一个 SqlSession 对象, 在参数和 SQL 完全一样的情况下, 只执行一次 SQL 语句(如果缓存没有过期)。

<!-- 默认为 `SESSION` -->
<setting name="localCacheScope" value="SESSION|STATEMENT"/>

SqlSession 一级缓存的工作流程:

  1. 对于某个查询,根据 statementId, params, rowBounds来构建一个 key 值,根据这个 key 值去缓存 Cache 中取出缓存结果​
  2. 判断从 Cache中根据特定的 key 值取的数据数据是否为空,即是否命中
  3. 如果命中,则直接将缓存结果返回
  4. 如果没命中
    • 去数据库中查询数据,得到查询结果
    • key 和查询到的结果分别作为 key, value 对存储到 Cache
    • 将查询结果返回
  • 不同的 SqlSession 之间的缓存是相互隔离的,多个 SqlSession 或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement
  • 一级缓存无法关闭,在作用域配置为 SESSION 时,可以通过配置 flushCache="true" 将作用域设为 statement,使得在查询后清空缓存
  • 任何的 UPDATE, INSERT, DELETE 语句都会清空缓存

二级缓存

二级缓存是用来解决一级缓存不能跨会话共享的问题的,范围是 namespace 级别的,可以被多个 SqlSession 共享(只要是同一个接口里面的相同方法,都可以共享),生命周期和应用同步(SqlSessionFactory 生命周期)。如果开启了二级缓存,并且 MapperSELECT 语句也配置使用了二级缓存,那么在执行查询的时候,会先从二级缓存中取输入,其次才是一级缓存,即 MyBatis 查询数据的顺序是:二级缓存 -> 一级缓存 -> 数据库。

<cache type="org.apache.ibatis.cache.impl.PerpetualCache" size="1024" eviction="LRU" flushInterval="120000" readOnly="false"/>
  • 二级缓存是事务性的,在执行查询时,只有完成事务提交或回滚,缓存才会获得更新。
  • 因为更新会刷新缓存,导致二级缓存失效,所以二级缓存适合在查询为主的应用中使用
  • 如果多个 namespace 中有针对于同一个表的操作,在一个 namespace 中刷新了缓存,另一个 namespace 中没有刷新,就会出现读到脏数据的情况,所以推荐在一个 Mapper 里面只操作单表的情况下使用
  • Mapper.xml 配置了 <cache> 后,SELECT 会被缓存,UPDATEDELETEINSERT 会刷新缓存;如果 Mapper.xml 没有配置 <cache>,则该 namespace 没有二级缓存,但依然会使用 CachingExecutor 包装对象执行 sql
  • 如果某些查询方法对数据的实时性要求很高,不需要二级缓存,可以在单个 Statement 上通过 useCache="false" 显式关闭二级缓存(默认为 true
  • 如果要让多个 namespace 共享一个二级缓存,可以使用 <cache-ref> 来解决
<cache-ref namespace="com.wuzz.crud.dao.DepartmentMapper" />

其他

mybatis认为,对于两次查询,如果以下条件都完全一样,那么就认为它们是完全相同的两次查询。

  1. 传入的 statementId
  2. 查询时要求的结果集中的结果范围
  3. 查询所产生的最终要传递给 statementSQL 语句字符串
  4. 传递给 statement 的参数值

插件

Mybatis 仅可以编写针对 ParameterHandlerResultSetHandlerStatementHandlerExecutor4 种接口的插件,Mybatis 使用 JDK 动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能。 实现 MybatisInterceptor 接口并重写 intercept 方法,在 Interceptor 类上使用 @Intercepts 注解指定要拦截的 方法参数类型 即可。