网站可以做的兼职,网站关键词分布,家电设计公司,开阿里巴巴网站建设流程MyBatis 缓存
MyBatis 是现在国内比较流行的 ORM 框架#xff0c;在学习 MyBatis 的时候#xff0c;不得不了解 MyBatis 的两级缓存#xff0c;要了解 MyBatis 的缓存#xff0c;先要了解 MyBatis 几个重要的对象
SqlSession - 对应的一次数据库会话#xff0c;由 SqlSe…MyBatis 缓存
MyBatis 是现在国内比较流行的 ORM 框架在学习 MyBatis 的时候不得不了解 MyBatis 的两级缓存要了解 MyBatis 的缓存先要了解 MyBatis 几个重要的对象
SqlSession - 对应的一次数据库会话由 SqlSessionFactory 的 openSession 创建一次会话并不代表只能执行一条 SQLMappedStatement - 存储了 SQL 对应的所有信息XMLStatementBuilder 解析 XML 或者注解的时候由 parseStatementNode 方法生成放入到 configuration 中保存Executor - 真正对数据库操作的对象由 Configuration 的 newExecutor 创建namespace - 用来区分 sql 命令和 statementid 一起生成的 key 值作为 sql 的唯一标识 MyBatis 一级缓存
首先一级缓存的配置有两种
SESSION默认STATEMENT
configurationsettingssetting namelocalCacheScope valueSESSION//settings
configuration所以 MyBatis 的一级缓存可以是 SqlSession 级别的也可以是 Statement 级别的 原理
当客户端执行 SQL 的时候会将查询结果封装到 SqlSession 的 ExecutorBaseExecutor 中的 localCache 属性中Executor 的 query 方法其底层是一个 HashMap
protected PerpetualCache localCache;private E ListE queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {ListE list;localCache.putObject(key, EXECUTION_PLACEHOLDER);try {list doQuery(ms, parameter, rowBounds, resultHandler, boundSql);} finally {localCache.removeObject(key);}// 放入缓存localCache.putObject(key, list);if (ms.getStatementType() StatementType.CALLABLE) {localOutputParameterCache.putObject(key, parameter);}return list;
}key 值为 MappedStatementId Offset Limit SQL SQL 中的参数一起构成 CacheKeyExecutor 的 createCacheKey 方法生成 Key 的方法 Overridepublic CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {if (closed) {throw new ExecutorException(Executor was closed.);}CacheKey cacheKey new CacheKey();cacheKey.update(ms.getId());cacheKey.update(rowBounds.getOffset());cacheKey.update(rowBounds.getLimit());cacheKey.update(boundSql.getSql());ListParameterMapping parameterMappings boundSql.getParameterMappings();TypeHandlerRegistry typeHandlerRegistry ms.getConfiguration().getTypeHandlerRegistry();// mimic DefaultParameterHandler logicfor (ParameterMapping parameterMapping : parameterMappings) {if (parameterMapping.getMode() ! ParameterMode.OUT) {Object value;String propertyName parameterMapping.getProperty();if (boundSql.hasAdditionalParameter(propertyName)) {value boundSql.getAdditionalParameter(propertyName);} else if (parameterObject null) {value null;} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {value parameterObject;} else {MetaObject metaObject configuration.newMetaObject(parameterObject);value metaObject.getValue(propertyName);}cacheKey.update(value);}}if (configuration.getEnvironment() ! null) {// issue #176cacheKey.update(configuration.getEnvironment().getId());}return cacheKey;}作为在市场叱咤了这么多年的框架当然会考虑在数据更新之后查到缓存的问题所以在更新数据的时候会将缓存清除此处是无差别攻击
Override
public int update(MappedStatement ms, Object parameter) throws SQLException {ErrorContext.instance().resource(ms.getResource()).activity(executing an update).object(ms.getId());if (closed) {throw new ExecutorException(Executor was closed.);}// 清除缓存clearLocalCache();return doUpdate(ms, parameter);
}如果想跳过一级缓存可以配置 select flushCache ture 即可 思考
现在系统都是分布式集群这种一级缓存应该也是有问题的 MyBatis 二级缓存
配置方式
config 配置
settingssetting namecacheEnabled valuetrue/
/settingsMapper.xml 配置
cache /mapper 接口
Mapper
CacheNamespace // 接口级别
public interface TestDao {Options(useCache true) // 方法级别Select({select * from test})Test getTest();
}statement 语句中配置
select id xxx useCachetrue ... /select可以理解 MyBatis 的二级缓存是 namespace 级别或者可以理解是 mapper 级别的
原理
MyBatis 的二级缓存是可以扩展很多的它的核心接口是 org.apache.ibatis.cache.Cache
public interface Cache {/*** return The identifier of this cache*/String getId();/*** param key* Can be any object but usually it is a {link CacheKey}* param value* The result of a select.*/void putObject(Object key, Object value);/*** param key* The key* return The object stored in the cache.*/Object getObject(Object key);/*** As of 3.3.0 this method is only called during a rollback* for any previous value that was missing in the cache.* This lets any blocking cache to release the lock that* may have previously put on the key.* A blocking cache puts a lock when a value is null* and releases it when the value is back again.* This way other threads will wait for the value to be* available instead of hitting the database.*** param key* The key* return Not used*/Object removeObject(Object key);/*** Clears this cache instance.*/void clear();/*** Optional. This method is not called by the core.** return The number of elements stored in the cache (not its capacity).*/int getSize();/*** Optional. As of 3.2.6 this method is no longer called by the core.* p* Any locking needed by the cache must be provided internally by the cache provider.** return A ReadWriteLock*/default ReadWriteLock getReadWriteLock() {return null;}
如果开启了二级缓存最后执行的是 CachingExecutor但是它其实是将 BaseExecutor 包装了一层的实现
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {executorType executorType null ? defaultExecutorType : executorType;executorType executorType null ? ExecutorType.SIMPLE : executorType;Executor executor;if (ExecutorType.BATCH executorType) {executor new BatchExecutor(this, transaction);} else if (ExecutorType.REUSE executorType) {executor new ReuseExecutor(this, transaction);} else {executor new SimpleExecutor(this, transaction);}if (cacheEnabled) {// 传入 BaseExecutor 进行包装executor new CachingExecutor(executor);}executor (Executor) interceptorChain.pluginAll(executor);return executor;
}二级缓存存储代码
private final TransactionalCacheManager tcm new TransactionalCacheManager();Override
public E ListE query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)throws SQLException {// 此处需要于 MappedStatement 绑定的 Cache如果打了标签默认是 Cache cache ms.getCache();if (cache ! null) {flushCacheIfRequired(ms);if (ms.isUseCache() resultHandler null) {ensureNoOutParams(ms, boundSql);SuppressWarnings(unchecked)// 先查询的是二级缓存ListE list (ListE) tcm.getObject(cache, key);if (list null) {// 这里是调用 BaseExecutor 的 query 方法list delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);// 此处是放入二级缓存tcm.putObject(cache, key, list); // issue #578 and #116}return list;}}return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}其中 TransactionalCacheManager 中的缓存属性为
// TransactionCache是装饰器对象对Cache进行增强
private final MapCache, TransactionalCache transactionalCaches new HashMap();TranactionalCache 的中缓存的属性为
public class TransactionalCache implements Cache {private static final Log log LogFactory.getLog(TransactionalCache.class);// 被增强的Cacheprivate final Cache delegate;// 提交事务时清空缓存的标识private boolean clearOnCommit;// 待提交的数据只有在事务提交时才会将数据存放在二级缓存中private final MapObject, Object entriesToAddOnCommit;// 缓存中没有命中的数据private final SetObject entriesMissedInCache;...
}默认的 Cache 是在构建器 XMLMapperBuilder 解析 mapper 的时候动态插入的
private void cacheElement(XNode context) {if (context ! null) {String type context.getStringAttribute(type, PERPETUAL);Class? extends Cache typeClass typeAliasRegistry.resolveAlias(type);String eviction context.getStringAttribute(eviction, LRU);Class? extends Cache evictionClass typeAliasRegistry.resolveAlias(eviction);Long flushInterval context.getLongAttribute(flushInterval);Integer size context.getIntAttribute(size);boolean readWrite !context.getBooleanAttribute(readOnly, false);boolean blocking context.getBooleanAttribute(blocking, false);Properties props context.getChildrenAsProperties();// 此处构建对应二级缓存的 CachebuilderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);}
}构建 Cache 的类型为被层层包装过了的 Cache
public Cache useNewCache(Class? extends Cache typeClass,Class? extends Cache evictionClass,Long flushInterval,Integer size,boolean readWrite,boolean blocking,Properties props) {Cache cache new CacheBuilder(currentNamespace).implementation(valueOrDefault(typeClass, PerpetualCache.class)).addDecorator(valueOrDefault(evictionClass, LruCache.class)).clearInterval(flushInterval).size(size).readWrite(readWrite).blocking(blocking).properties(props).build();configuration.addCache(cache);currentCache cache;return cache;
}public Cache build() {setDefaultImplementations();Cache cache newBaseCacheInstance(implementation, id);setCacheProperties(cache);// issue #352, do not apply decorators to custom cachesif (PerpetualCache.class.equals(cache.getClass())) {for (Class? extends Cache decorator : decorators) {cache newCacheDecoratorInstance(decorator, cache);setCacheProperties(cache);}cache setStandardDecorators(cache);} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {cache new LoggingCache(cache);}return cache;
}// 这里将 Cache 一层一层往里面包装看方法名称也知道是装饰器模式加强
private Cache setStandardDecorators(Cache cache) {try {MetaObject metaCache SystemMetaObject.forObject(cache);if (size ! null metaCache.hasSetter(size)) {metaCache.setValue(size, size);}if (clearInterval ! null) {cache new ScheduledCache(cache);((ScheduledCache) cache).setClearInterval(clearInterval);}if (readWrite) {cache new SerializedCache(cache);}cache new LoggingCache(cache);cache new SynchronizedCache(cache);if (blocking) {cache new BlockingCache(cache);}return cache;} catch (Exception e) {throw new CacheException(Error building standard cache decorators. Cause: e, e);}
}思考
二级缓存使用装饰者模式对 BaseExecutor 的方法进行增强这种编码风格在日常编码中也可以使用