MyBatis源码分析--一级缓存、二级缓存原理

前言:

有点项目经验的朋友都知道缓存的重要性是不言而喻的,不仅仅我们在开发项目业务功能的时候使用了各种缓存,框架在设计的时候也有框架层面的缓存,尤其在查询多的场景下,缓存可以大大的减少数据库访问,提升系统效率,Mybatis 也提供了缓存,分别为一级缓存和二级缓存,默认的情况下,Mybatis 只开启一级缓存。

Mybatis 相关知识传送门

初识 MyBatis 【MyBatis 核心概念】

MyBatis 源码分析–SqlSessionFactory

MyBatis 源码分析–获取SqlSession

MyBatis 源码分析-- getMapper(获取Mapper)

MyBatis 源码分析-- SQL请求执行流程( Mapper 接口方法的执行的过程)

MyBatis 源码分析-- 插件(拦截器)原理

MyBatis 插件(拦截器)实战(自定义实现拦截器)

一级缓存

什么是一级缓存?

一级缓存是 SqlSession 级别的缓存,在操作数据库时需要构造 SqlSession 对象,在对象中有一个 Map(HashMap)用于存储缓存数据,不同的 SqlSession 之间的缓存是互相不影响的,即同一个 SqlSession 对象, 在参数和 SQL 完全一样的情况下,多次查询只执行一次 SQL 语句,因为第一次查询后,MyBatis 会将其放在缓存中,后面再次查询的时候,如果没有声明需要刷新,且缓存没有超时的情况下,SqlSession 会取出当前缓存的数据,不会再次发送 SQL 到数据库进行查询,这就是一级缓存。

一级缓存编码验证:

代码示例:

@Transactional(rollbackFor = Exception.class)
@Override
public void queryById(Long id) {selectById(id);log.info("第一次查询");selectById(id);log.info("第二次查询");
}

演示结果:

在这里插入图片描述
通过输出日志可以知道,两次查询只执行了一次查询数据库的操作,表名一级缓存生效了(Mybatis 默认开启一级缓存)。

注意:如果想要使用 Mybatis 的一级缓存,需要保证是在同一个事务中,只有在同一个事务中,才能获取到同一个 SqlSession。

一级缓存失效的场景:

  • 使用不同的 SqlSession。
  • 两次查询的查询条件不一致。
  • 两次查询之间有增删改操作。
  • 两次查询之间手动清除了一级缓存。

一级缓存源码分析

一级缓存的原理其实在前文分支 SQL 的执行过程的时候已经分析过了,这里我们在回忆一下,在 BaseExecutor#query 方法中会判断一级缓存中是否有数据,有就返回,没有才会去查询数据库。

//org.apache.ibatis.executor.BaseExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {//错误上下文ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());//执行器关闭 抛出异常if (this.closed) {throw new ExecutorException("Executor was closed.");} else {//queryStack 查询堆栈 防止递归查询重复处理缓存  是否刷新缓存 flushCacheRequired 是 true 清除一级缓存if (this.queryStack == 0 && ms.isFlushCacheRequired()) {//清除一级缓存this.clearLocalCache();}List list;try {//查询堆栈 +1++this.queryStack;//从一级缓存中获取数据list = resultHandler == null ? (List)this.localCache.getObject(key) : null;if (list != null) {//从一级缓存中获取数据this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);} else {//从数据库查询数据list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);}} finally {//查询堆栈-1--this.queryStack;}if (this.queryStack == 0) {Iterator i$ = this.deferredLoads.iterator();while(i$.hasNext()) {BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)i$.next();deferredLoad.load();}this.deferredLoads.clear();if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {this.clearLocalCache();}}return list;}
}

一级缓存的存入时机就是每次查询数据库的时候,只要查询了数据库,结果都会缓存到一级缓存中,这也就是一级缓存默认打开的原因。

//org.apache.ibatis.executor.BaseExecutor#queryFromDatabase
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {//一级缓存 存储一个占位符this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);List list;try {//执行数据库查询list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);} finally {//清除占位符缓存this.localCache.removeObject(key);}//存入一级缓存this.localCache.putObject(key, list);if (ms.getStatementType() == StatementType.CALLABLE) {this.localOutputParameterCache.putObject(key, parameter);}return list;
}

我们知道 Mybatis 的 INSERT、UPDATE、DELETE 最终执行的都是 update 方法,在 update 方法中我们看到了清除一级缓存的代码,这就是为什么两次查询之间有 INSERT、UPDATE、DELETE 操作时候,就无法使用一级缓存的原因。

//org.apache.ibatis.executor.BaseExecutor#update
public int update(MappedStatement ms, Object parameter) throws SQLException {ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());if (this.closed) {throw new ExecutorException("Executor was closed.");} else {//清除一级缓存this.clearLocalCache();return this.doUpdate(ms, parameter);}
}

一级缓存源码结构

一级缓存的源码结构很简单,就是一个简单的 Map 结构,提供了一些常用的 put、get、remove、clear 方法,本质就是内存中的一个 Map。

public class PerpetualCache implements Cache {private final String id;private final Map<Object, Object> cache = new HashMap();public PerpetualCache(String id) {this.id = id;}public String getId() {return this.id;}public int getSize() {return this.cache.size();}public void putObject(Object key, Object value) {this.cache.put(key, value);}public Object getObject(Object key) {return this.cache.get(key);}public Object removeObject(Object key) {return this.cache.remove(key);}public void clear() {this.cache.clear();}public boolean equals(Object o) {if (this.getId() == null) {throw new CacheException("Cache instances require an ID.");} else if (this == o) {return true;} else if (!(o instanceof Cache)) {return false;} else {Cache otherCache = (Cache)o;return this.getId().equals(otherCache.getId());}}public int hashCode() {if (this.getId() == null) {throw new CacheException("Cache instances require an ID.");} else {return this.getId().hashCode();}}
}

缓存 key 的生成方法 createCacheKey 源码分析

从源码中我们可以知道 cacheKey 是根据 MappedStatement id + RowBounds offset + RowBounds limit + SQL + Parameter值 + Environment id 来确定唯一性的。

//org.apache.ibatis.executor.BaseExecutor#createCacheKey
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {//判断执行器是否关闭if (this.closed) {throw new ExecutorException("Executor was closed.");} else {//创建 缓存key 对象CacheKey cacheKey = new CacheKey();//根据接口的全限定类名+方法名更新 cacheKeycacheKey.update(ms.getId());//根据查询数据的偏移量更新 cacheKeycacheKey.update(rowBounds.getOffset());//根据查询数据的条数更新 cacheKeycacheKey.update(rowBounds.getLimit());//根据sql 语句更新 cacheKeycacheKey.update(boundSql.getSql());//其实是从查询 sql 占位符中解析出的参数元数据信息 然后进行遍历 获取每个参数名 并从查询接口传入的参数中获取相应的参数值List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();//获取类型处理器注册表TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();//循环遍历入参 for(int i = 0; i < parameterMappings.size(); ++i) {//获取参数映射ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i);//参数类型判断判断if (parameterMapping.getMode() != ParameterMode.OUT) {//获取参数属性名称String propertyName = parameterMapping.getProperty();//参数值Object value;//是否有附加参数if (boundSql.hasAdditionalParameter(propertyName)) {//根据参数名称获取参数值value = boundSql.getAdditionalParameter(propertyName);} else if (parameterObject == null) {//参数为空value = null;} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {//参数类型注册表中有参数类型 value = parameterObject;} else {//MetaObject提供了一些便捷的方法 可以根据 parameterObject 类型 获取相应 propertyName 值的方法 不需要直接使用反射操作MetaObject metaObject = this.configuration.newMetaObject(parameterObject);//使用  metaObject 获取参数值value = metaObject.getValue(propertyName);}//根据环境id更新 cacheKeyif (this.configuration.getEnvironment() != null) {cacheKey.update(this.configuration.getEnvironment().getId());}//根据环境 value 更新 cacheKeycacheKey.update(value);}}return cacheKey;}
}

CacheKey#update 方法源码分析

CacheKey#update 会把生产的唯一 CacheKey 进行 HashCode,并更新到 updateList 中,判断两个 CacheKey 对象相等的充分必要条件是两个对象代表的组合序列中的每个元素必须都相等,为了避免每次比较都要进行一次循环(遍历组合List),CacheKey 采用 hashCode–>checksum–>count–>updateList 的顺序比较,只要有一个不相等,则视为两个 CacheKey 对象不相等。

//org.apache.ibatis.cache.CacheKey#update
public void update(Object object) {//如果 object 为空 hashcode 就为1 不为 null  就获取 hashcodeint baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);//count ++++this.count;//checksum 等于 checksum +object的 hashcodethis.checksum += (long)baseHashCode;//baseHashCode 等于 baseHashCode 乘以 countbaseHashCode *= this.count;//重新计算 hashcodethis.hashcode = this.multiplier * this.hashcode + baseHashCode;//更新到列表中this.updateList.add(object);
}

二级缓存

什么是二级缓存?

二级缓存是存户在 SqlSessionFactory 中,二级缓存和名称空间绑定,也就是说通常所说的二级缓存是 Mapper 级别的缓存,即多个 SqlSession 共享同一个 Mapper 命名空间下的缓存,它的作用是缓存 Mapper 执行的结果,避免频繁地访问数据库,提高系统的性能。

配置二级缓存

Mybatis 二级缓存默认是关闭的,二级缓存有全局开关、局部开关和在某个查询 SQL 上配置使用缓存,可以根据自己的需求灵活配置。

全局开启二级缓存:

在 mybatis-config.xml 中如下配置。

<settings><setting name="cacheEnabled" value="true"/>
</settings>

局部开启二级缓存:

在 Mapper.xml 文件中开启二级缓存。

<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>

标签解释:

  • eviction:清除缓存的策略,FIFO 表示先进先出的策略,默认是 LRU 最近最少使用。
  • flushInterval:缓存刷新时间间隔,缓存多长时间刷新一次,默认不清空,设置一个毫秒值。
  • size:缓存存放多少个元素。
  • readOnly:是否只读,true 只读,MyBatis 认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。
  • type:指定自定义缓存的全类名,自己实现Cache 接口即可。
  • blocking:缓存中找不到对应的key,是否会一直 blocking,直到有对应的数据进入缓存。

eviction 缓存回收策略

  • FIFO :先进先出,按照缓存进入的顺序来移除。
  • LRU:最近最少使用,移除最长时间不被使用的对象。
  • SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象。
  • WEAK:弱引用,更积极的移除基于垃圾收集器和弱引用规则的对象。

在某个查询 SQL 上配置使用缓存,如下:

<select id="selectUser" resultType="com.my.study.User" useCache="true" flushCache="true">select * from user where id = #{id}
</select>

二级缓存的创建

二级缓存的创建,其实就是解析 标签,这个解析动作是在构建 SqlSessionFactory 的过程中做的,前文在分析 SqlSessionFactory 的创建过程中有详细讲解,这里只分析 解析 标签的源码,如下:

SqlSession 创建传送门

//org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement
private void configurationElement(XNode context) {try {//获取 namespaceString namespace = context.getStringAttribute("namespace");if (namespace != null && !namespace.isEmpty()) {//设置 namespacethis.builderAssistant.setCurrentNamespace(namespace);//解析 cache-ref 标签this.cacheRefElement(context.evalNode("cache-ref"));//解析 cache 标签this.cacheElement(context.evalNode("cache"));//解析映射参数 parameterMap this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));//解析结果集 resultMapthis.resultMapElements(context.evalNodes("/mapper/resultMap"));//解析 sqlthis.sqlElement(context.evalNodes("/mapper/sql"));//构建 crud 语句 this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));} else {throw new BuilderException("Mapper's namespace cannot be empty");}} catch (Exception var3) {throw new BuilderException("Error parsing Mapper XML. The XML location is '" + this.resource + "'. Cause: " + var3, var3);}
}

XMLMapperBuilder#cacheElement 源码分析

XMLMapperBuilder#cacheElement 源码就很简单了,其实就是对 Cache 标签的解析,解析 Cache 标签的各个属性,然后构建一个 Cache 对象返回。

//org.apache.ibatis.builder.xml.XMLMapperBuilder#cacheElement
private void cacheElement(XNode context) {if (context != null) {//获取缓存类名 这里如果我们定义了<cache/>中的 type 就使用自定义的Cache类 否则使用和一级缓存相同的PerpetualCacheString type = context.getStringAttribute("type", "PERPETUAL");//从类型别名注册表中获取缓存类名的 classClass<? extends Cache> typeClass = this.typeAliasRegistry.resolveAlias(type);//缓存淘汰策略 默认 LRUString eviction = context.getStringAttribute("eviction", "LRU");//从类型别名注册表中获取缓存类名的 classClass<? extends Cache> evictionClass = this.typeAliasRegistry.resolveAlias(eviction);//获取缓存刷新时间间隔Long flushInterval = context.getLongAttribute("flushInterval");//缓存存放的元素个数Integer size = context.getIntAttribute("size");//只读属性 默认 falseboolean readWrite = !context.getBooleanAttribute("readOnly", false);//缓存中找不到对应的key 是否会一直 blocking 直到有对应的数据进入缓存 默认 falseboolean blocking = context.getBooleanAttribute("blocking", false);Properties props = context.getChildrenAsProperties();//构建一个新的二级缓存对象this.builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);}}//org.apache.ibatis.builder.MapperBuilderAssistant#useNewCache
public Cache useNewCache(Class<? extends Cache> typeClass, Class<? extends Cache> evictionClass, Long flushInterval, Integer size, boolean readWrite, boolean blocking, Properties props) {//创建 cache 对象Cache cache = (new CacheBuilder(this.currentNamespace)).implementation((Class)this.valueOrDefault(typeClass, PerpetualCache.class)).addDecorator((Class)this.valueOrDefault(evictionClass, LruCache.class)).clearInterval(flushInterval).size(size).readWrite(readWrite).blocking(blocking).properties(props).build();//添加到 configuration 中this.configuration.addCache(cache);//将 cache 赋值给 MapperBuilderAssistant.currentCachethis.currentCache = cache;return cache;
}

二级缓存在查询过程中的使用源码分析

二级缓存的使用再 CachingExecutor#query 方法中有体现,如下:

//CachingExecutor#query 方法会从 MappedStatement 中获取 SQL 信息,创建缓存 key 并执行查询,Configuration 中 cacheEnabled 属性值默认为 true,因此会执行 CachingExecutor 的 query方法。
//org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {//获取 SQL 基本信息BoundSql boundSql = ms.getBoundSql(parameterObject);//创建缓存 keyCacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);//执行查询return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}//CachingExecutor#query 会判断是否使用了缓存,如果允许使用缓存会先从二级缓存查询,二级缓存中查询不到才会去查询一级缓存或者数据库,如果二级缓存为空或者不允许使用缓存就会直接去查询一级缓存或者数据库。
//org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {//获取 二级缓存Cache cache = ms.getCache();//为空判断if (cache != null) {//是否刷新缓存//select 时候 flushCacheRequired 默认是false 不清除二级缓存 //insert update delete 时候 flushCacheRequired 是 true 会清除二级缓存this.flushCacheIfRequired(ms);//如果使用了缓存  ResultHandler 不为空 if (ms.isUseCache() && resultHandler == null) {//对存储过程的处理 确保没有存储过程 如果有存储过程 就报错this.ensureNoOutParams(ms, parameterObject, boundSql);//从二级缓存中查询数据 TransactionalCacheManagerList<E> list = (List)this.tcm.getObject(cache, key);//结果为空 判断if (list == null) {//为空 BaseExexutor 执行查询数据库list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);//加入二级缓存this.tcm.putObject(cache, key, list);}return list;}}//缓存为空 BaseExexutor 直接查询数据库return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

TransactionalCacheManager 源码分析

二级缓存 Cache 对象是从 MappedStatement 中获取的,一个 Mapper 对应一个 MapperStatment,MappedStatement(SQL语句信息) 对象是存在全局配置中,多个 CachingExecutor 多可以获取到,这样就可以在多个线程之间共用了,会存在线程安全问题,同样多个 SqlSession 使用同一个 Cache 也会出现数据脏读问题,因此使用了事务缓存管理器 TransactionalCacheManager 来管理二级缓存,TransactionalCacheManager 使用一个 Map 维护了 Cache 和 TransactionalCache 的关系,并提供了管理缓存的方法,但真正管理缓存的时候 TransactionalCache。

package org.apache.ibatis.cache;import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.apache.ibatis.cache.decorators.TransactionalCache;public class TransactionalCacheManager {//维护 Cache ransactionalCache 之间的关系private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap();public TransactionalCacheManager() {}//清除缓存public void clear(Cache cache) {this.getTransactionalCache(cache).clear();}//获取缓存public Object getObject(Cache cache, CacheKey key) {return this.getTransactionalCache(cache).getObject(key);}//存入缓存public void putObject(Cache cache, CacheKey key, Object value) {this.getTransactionalCache(cache).putObject(key, value);}public void commit() {Iterator var1 = this.transactionalCaches.values().iterator();while(var1.hasNext()) {TransactionalCache txCache = (TransactionalCache)var1.next();txCache.commit();}}public void rollback() {Iterator var1 = this.transactionalCaches.values().iterator();while(var1.hasNext()) {TransactionalCache txCache = (TransactionalCache)var1.next();txCache.rollback();}}//获取 TransactionalCacheprivate TransactionalCache getTransactionalCache(Cache cache) {return (TransactionalCache)this.transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);}
}

TransactionalCache 源码分析

TransactionalCache 是事务缓存装饰器,可以为 Cache 增加事务功能,真正操作缓存的方法都在 TransactionalCache 中,TransactionalCache 中真正的二级缓存存在在 delegate 中,读取缓存从 delegate 中读取,存入缓存的时候会先存入到 entriesToAddOnCommit 中,只有正真提交事务的时候,才会把缓存加入到 delegate 真正的二级缓存中。

package org.apache.ibatis.cache.decorators;import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;public class TransactionalCache implements Cache {private static final Log log = LogFactory.getLog(TransactionalCache.class);//二级缓存对象private final Cache delegate;//在commit的时候是否清除数据 标记位private boolean clearOnCommit;//存放缓存中没有的对象 只有在 commit 的时候 才会真正加入到缓存中private final Map<Object, Object> entriesToAddOnCommit;//事务提交前 在二级缓存中没有找到这个对象就加入 entriesMissedInCacheprivate final Set<Object> entriesMissedInCache;//构造方法public TransactionalCache(Cache delegate) {this.delegate = delegate;this.clearOnCommit = false;this.entriesToAddOnCommit = new HashMap();this.entriesMissedInCache = new HashSet();}public String getId() {return this.delegate.getId();}public int getSize() {return this.delegate.getSize();}//获取缓存public Object getObject(Object key) {//从二级缓存中获取对象Object object = this.delegate.getObject(key);//为空if (object == null) {//key 加入到 entriesMissedInCachethis.entriesMissedInCache.add(key);}//更新二级缓存的时候 不会直接清除二级缓存 而是会将 clearOnCommit 改为true//clearOnCommit 为true 返回 null  否则返回查询到的缓存对象return this.clearOnCommit ? null : object;}//存入缓存public void putObject(Object key, Object object) {//这里只是加入到 entriesToAddOnCommit 中this.entriesToAddOnCommit.put(key, object);}public Object removeObject(Object key) {return null;}public void clear() {this.clearOnCommit = true;this.entriesToAddOnCommit.clear();}//提交事务public void commit() {if (this.clearOnCommit) {//如果提交时候清除缓存 就执行清空操作this.delegate.clear();}//将 entriesToAddOnCommit 加入二级缓存this.flushPendingEntries();//加入完成之后 执行重置操作 重置 entriesToAddOnCommit entriesMissedInCache this.reset();}//回滚public void rollback() {//清除二级缓存this.unlockMissedEntries();//重置操作  重置 entriesToAddOnCommit entriesMissedInCache this.reset();}//重置操作private void reset() {this.clearOnCommit = false;this.entriesToAddOnCommit.clear();this.entriesMissedInCache.clear();}//刷新待处理的数据 其实就是加入二级缓存private void flushPendingEntries() {//迭代遍历 entriesToAddOnCommit Iterator var1 = this.entriesToAddOnCommit.entrySet().iterator();while(var1.hasNext()) {Entry<Object, Object> entry = (Entry)var1.next();//加入二级缓存this.delegate.putObject(entry.getKey(), entry.getValue());}//迭代遍历 entriesMissedInCachevar1 = this.entriesMissedInCache.iterator();while(var1.hasNext()) {Object entry = var1.next();if (!this.entriesToAddOnCommit.containsKey(entry)) {//entriesToAddOnCommit 中不存在 则加入二级缓存 value 为  nullthis.delegate.putObject(entry, (Object)null);}}}//如果回滚就执行该防范private void unlockMissedEntries() {//迭代遍历 entriesMissedInCacheIterator var1 = this.entriesMissedInCache.iterator();while(var1.hasNext()) {Object entry = var1.next();try {//从二级缓存中移出对象this.delegate.removeObject(entry);} catch (Exception var4) {log.warn("Unexpected exception while notifiying a rollback to the cache adapter. Consider upgrading your cache adapter to the latest version. Cause: " + var4);}}}
}

二级缓存生效的时机

我们根据 DefaultSqlSession#commit 方法的源码跟进去,发现最后调用了 TransactionalCache#commit 方法,也就是二级缓存的生效时机是在 SqlSession 提交之后,这样就避免了单机情况下的脏读问题。

//org.apache.ibatis.session.defaults.DefaultSqlSession#commit()
public void commit() {this.commit(false);
}//org.apache.ibatis.session.defaults.DefaultSqlSession#commit(boolean)
public void commit(boolean force) {try {this.executor.commit(this.isCommitOrRollbackRequired(force));this.dirty = false;} catch (Exception var6) {throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + var6, var6);} finally {ErrorContext.instance().reset();}}//org.apache.ibatis.executor.CachingExecutor#commit
public void commit(boolean required) throws SQLException {this.delegate.commit(required);this.tcm.commit();
}//org.apache.ibatis.cache.TransactionalCacheManager#commit
public void commit() {Iterator var1 = this.transactionalCaches.values().iterator();while(var1.hasNext()) {TransactionalCache txCache = (TransactionalCache)var1.next();txCache.commit();}}//org.apache.ibatis.cache.decorators.TransactionalCache#commit
public void commit() {if (this.clearOnCommit) {this.delegate.clear();}this.flushPendingEntries();this.reset();
}

二级缓存更新时机

同样从 DefaultSqlSession#update 方法入手分析,发现最终调用了 TransactionalCache#clear 方法,TransactionalCache#clear 方法并没有正在的更新或者清除二级缓存,而是将 clearOnCommit 提交事务时候是否清除缓存改为 true(默认是false),事务提交时候会进行判断,如果 clearOnCommit 为 true,就会清除二级缓存,同样在查询二级缓存的时候也会判断 clearOnCommit 属性,如果 clearOnCommit 为 true,则直接返回 null,来保证数据的准确性。

//org.apache.ibatis.session.defaults.DefaultSqlSession#update(java.lang.String)
public int update(String statement) {return this.update(statement, (Object)null);
}//org.apache.ibatis.session.defaults.DefaultSqlSession#update(java.lang.String, java.lang.Object)
public int update(String statement, Object parameter) {int var4;try {this.dirty = true;MappedStatement ms = this.configuration.getMappedStatement(statement);var4 = this.executor.update(ms, this.wrapCollection(parameter));} catch (Exception var8) {throw ExceptionFactory.wrapException("Error updating database.  Cause: " + var8, var8);} finally {ErrorContext.instance().reset();}return var4;
}//org.apache.ibatis.executor.CachingExecutor#update
public int update(MappedStatement ms, Object parameterObject) throws SQLException {this.flushCacheIfRequired(ms);return this.delegate.update(ms, parameterObject);
}//org.apache.ibatis.executor.CachingExecutor#flushCacheIfRequired
private void flushCacheIfRequired(MappedStatement ms) {Cache cache = ms.getCache();if (cache != null && ms.isFlushCacheRequired()) {this.tcm.clear(cache);}}//org.apache.ibatis.cache.TransactionalCacheManager#clear
public void clear(Cache cache) {this.getTransactionalCache(cache).clear();
}//org.apache.ibatis.cache.decorators.TransactionalCache#clear
public void clear() {this.clearOnCommit = true;this.entriesToAddOnCommit.clear();
}

总结:二级缓存实现了 Sqlsession 之间的缓存数据共享,属于 namespace/Mapper 级别的缓存,具有丰富的缓存淘汰策略,二级缓存使用 TransactionalCache 来解决脏读的问题,只有事务提交的时候,对应的数据才会放入到二级缓存中,来避免脏读问题,同时需要注意的是默认的情况下,Mybatis将一二级缓存都存储到本地缓存中,因此在分布式情况下,二级缓存还是会出现脏读问题,分布式情况下不建议使用二级缓存。

欢迎提出建议及对错误的地方指出纠正。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/36113.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微前端框架是为了解决项目应用在大型项目中带来的复杂性和维护难题而提出的技术方案。

微前端框架是为了解决单页应用&#xff08;SPA&#xff09;在大型项目中带来的复杂性和维护难题而提出的技术方案。Qiankun.js、MicroApp 和 Wujie 是三种流行的微前端框架。以下是对这三种框架的优缺点分析&#xff1a; Qiankun.js 优点 成熟度高&#xff1a;Qiankun.js 基…

【知识学习】阐述Unity3D中FogLOD的概念及使用方法示例

在Unity3D中&#xff0c;Fog&#xff08;雾效&#xff09;和LOD&#xff08;Level of Detail&#xff0c;细节层次&#xff09;是两种用于提高场景视觉效果和性能的技术。 Fog&#xff08;雾效&#xff09; 雾效是一种视觉效果&#xff0c;用于模拟大气中的雾或烟&#xff0c…

YOLOv8数据集标注

1 简介 数据集是必不可少的部分&#xff0c;数据集的优劣直接影响训练效果。一般来说&#xff0c;一个完整的数据集应该包括训练集、测试集和验证集。通常&#xff0c;数据集会被划分为训练集和测试集&#xff0c;比如将数据集的70%用作训练集&#xff0c;30%用作测试集。在进行…

信号处理——时频分析

经典傅里叶变换的限制&#xff1a; 1、只能反映信号的整体特性&#xff1b;&#xff08;完全是时域或频域&#xff09; 2、要求信号满足平稳条件&#xff1b; 3、必须获得时域中的全部信息。 所以引入时频分析&#xff0c;同时使用时间和频率的联合函数来表示信号。 1 时频…

提高数据融合效率和数据成果质量工作流的可行性分析

第一章 引言 本文基于对框架数据、地名地址数据以及变更调查数据为主体数据源的分析&#xff0c;结合数据融合中分层数据处理原则和内容&#xff0c;从数据管理者、数据应用的角度提出数据质量的定位、需求定位&#xff0c;归纳数据融合过程中存在的困难&#xff0c;提出了数据…

FANUC喷涂机器人P-350iA电机过热维修解决方案

发那科喷涂机器人作为自动化喷涂生产线的重要组成部分&#xff0c;其性能稳定性和可靠性对于生产效率和产品质量具有重要影响。然而&#xff0c;在实际使用过程中&#xff0c;FANUC喷涂机器人P-350iA电机过热故障问题往往成为影响其正常运行的主要因素之一。 FANUC机器人M-100…

一款开源免费的现代化风格的Avalonia控件库

前言 Citrus.Avalonia是一款开源&#xff08;MIT License&#xff09;、免费的现代化风格的Avalonia控件库。 Avalonia介绍 Avalonia是一个强大的框架&#xff0c;使开发人员能够使用.NET创建跨平台应用程序。它使用自己的渲染引擎绘制UI控件&#xff0c;确保在Windows、mac…

推荐系统数据集——Amazon-Book

在推荐系统中&#xff0c;像Amazon-Book这样的数据集通常包含用户和物品的交互信息。为了训练模型&#xff0c;这些数据需要转换成适合模型输入的格式。在这种情况下&#xff0c;item_list和user_list需要转换成train.txt文件&#xff0c;通常包含用户ID和物品ID的交互记录。 …

你的生日是星期几?HTML+JavaScript帮你列出来

0 源起 上周末&#xff0c;大宝发现今年自己的生日不是周末&#xff0c;这样就不好约同学和好友一起开生日Party了&#xff0c;很是郁闷。一直嘀咕自己哪年的生日才是周末。 于是我用JavaScript写了一个小程序来帮她测算了未来100年中每年的生日分别是星期几。 1 设计交互界面…

搭建大型分布式服务(四十一)SpringBoot 整合多个kafka数据源-支持亿级消息生产者

系列文章目录 文章目录 系列文章目录前言一、本文要点二、开发环境三、原项目四、修改项目五、测试一下五、小结 前言 本插件稳定运行上百个kafka项目&#xff0c;每天处理上亿级的数据的精简小插件&#xff0c;快速上手。 <dependency><groupId>io.github.vipjo…

【ARM】MCU和SOC的区别

【更多软件使用问题请点击亿道电子官方网站】 1、 文档目标 了解SOC芯片和MCU芯片的区别 2、 问题场景 用于了解SOC芯片和MCU芯片的区别&#xff0c;内部结构上的区别。 3、软硬件环境 1&#xff09;、软件版本&#xff1a;无 2&#xff09;、电脑环境&#xff1a;无 3&am…

Java——枚举

1. 概念 枚举是在JDK1.5之后引入的&#xff0c;主要用途是&#xff1a;将一组常量组织起来&#xff0c;在这之前表示一组常量通常使用定义常量的方式&#xff1a; public static final int RED 1; public static final int GREEN 2; public static final int BLACK 3;但是…

第 12 课:基于隐语的VisionTransformer框架

基于之前MPC的基础知识&#xff0c;本讲主要内容是MPCViT基于SecretFlow的VisionTransformer框架&#xff0c;主要从神经网络架构&#xff0c;隐私推理框架和实验结果三方面介绍。 一、MPCViT&#xff1a;安全且高效的MPC友好型 Vision Transformer架构 MPCViT隐私推理总体框架…

QT中子工程的创建,以及如何在含有库的子工程项目中引用主项目中的qt资源

1、背景 在qt中创建多项目类型,如下: CustomDll表示其中的一个动态库子项目; CustomLib表示其中的一个静态库子项目; MyWidget表示主项目窗口(main函数所在项目); 2、qrc资源的共享 如何在CustomDll和CustomLib等子项目中也同样使用 MyWidget项目中的qrc资源呢??? 直…

【项目实训】后端逻辑完善

经测试&#xff0c;我们决定前端可以同时选择多个类型的岗位进行查询&#xff0c;以显示相应的公司岗位信息 于是&#xff0c;修改后端函数的逻辑&#xff1a; 后端 首先&#xff0c;因为要对checkList中的job_name进行模糊匹配查询&#xff0c;于是使用以下代码&#xff1a…

【科学计算与可视化】3. Matplotlib 绘图基础

安装 pip install matplotlib 官方文档 https://matplotlib.org/stable/api/pyplot_summary.html 主要介绍一些图片绘制的简要使用&#xff0c;更加详细和进阶需要可参考 以上官方文档。 1 绘制基础 方法名说明title()设置图表的名称xlabel()设置 x 轴名称ylabel()设置 y 轴…

负载组指南说明-负载柜

什么是负载组&#xff1f; 负载组是一种设备&#xff0c;旨在准确模拟电源在实际应用中看到的负载。这种负载组可以用电阻、电感或电容元件构建。它是一种电阻装置&#xff0c;以热量的形式消散一定量的能量&#xff0c;可以通过自然对流、强制空气或水冷系统去除。 为什么要使…

江协科技51单片机学习- p11 Proteus安装模拟51单片机

前言&#xff1a; 本文是根据哔哩哔哩网站上“江协科技51单片机”视频的学习笔记&#xff0c;在这里会记录下江协科技51单片机开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了江协科技51单片机教学视频和链接中的内容。 引用&#xff1a; Proteus快速入门&…

可溶性聚四氟乙烯离子交换柱PFA层析柱微柱一体成型

PFA微柱&#xff0c;也叫PFA层析柱、PFA离子交换柱等&#xff0c;主要用于地质同位素超净化、痕量、超痕量、微量元素分析实验室。 规格参考&#xff1a;1.5ml、15ml、30ml等。 其主要特性有&#xff1a; 1、PFA层析柱&#xff08;微柱&#xff09;专为离子交换设计&#xff…

SAP ERP公有云(全称 SAP S/4HANA Cloud Public Edition),赋能企业成为智能可持续的企业

在数字化浪潮中&#xff0c;每一家企业都需要应对快速的市场变化&#xff0c;不断追求降本增效&#xff0c;为创新提供资源&#xff0c;发展新的业务模式&#xff0c;安全无忧地完成关键任务系统的转型。 10年前&#xff0c;SAP进入云领域&#xff0c;用云ERP和覆盖全线业务的云…