Talking about mybatis-Cache


Every time before writing an article, I like to have my thoughts first. Today’s topic is to read more English documents, and there are so many benefits! Under what circumstances should cache issues be considered? In general microservice architecture design, distributed applications may cause dirty data, especially in the case of spikes, you need to pay special attention (access to external secondary cache)

Introduction

Insert picture description hereThe document has clearly explained what cashe, what is the primary cache and secondary cache. I personally sum it up as follows: When the cache is the same query, multiple queries will not be returned from the database when there is no update, submission, rollback, or shutdown, but directly from the cache implementation class of Cashe. The first level cache resides in the sqlsesson cache, the second level cache resides in the mapper level cache, the way to enable the first level cache is by setting localCacheScope=STATEMENT , the way to enable the second level cache is to configure cacheEnabled to true and mapper file configuration cache

Source code interpretation

  1. When constructing sqlsessionFactory, construct Executor, the specific construction method is DefaultSqlSessionFactory#openSessionFromConnection, we pay more attention to Configuration#newExecutor inside
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) {
    
     // 这里默认启用CachingExecutor动态代理
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor); // 执行拦截器
    return executor;
  }
  1. From the above, we can see that the object we are more concerned about is CachingExecutor, and let’s look at CachingExecutor to reverse how to achieve the second level cache.
@Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    
    
    Cache cache = ms.getCache(); // 这里是我们关注的二级缓存对象,此对象是从MappedStatement中获取
    if (cache != null) {
    
    
      flushCacheIfRequired(ms); // 这里判断是否有此缓存和是否需要清空缓存,若没有则存放在TransactionalCacheManager#transactionalCaches中
      if (ms.isUseCache() && resultHandler == null) {
    
     // 判断是否有Cache启动
        ensureNoOutParams(ms, boundSql); 
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key); // 缓存中获取
        if (list == null) {
    
     // 不存在则查询
          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); // 不启用缓存则查询本地缓存
  }
  1. From the above cache execution code, we can see that the external cache is obtained from the MappedStatement. Next, let’s see how the cache is constructed. View the source code to see that MappedStatement#cache is added to the cache. If you continue to follow up, then you can add it to MappedStatement#addMappedStatement, and then Up one level is MapperBuilderAssistant#useNewCache build. Next we look at the construction method
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)) // 默认PerpetualCache缓存,当然这里不支持分布式
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
  }

At this time, the attribute Id of Cache is that currentNamespace and MappedStatement are constructed at the same time, and then the XMLMapperBuilder#cacheElement method is found in the upper layer, and finally the element cach in the configurationElement judges whether to construct. In the end, the truth became clear. In fact, the second-level cache is built by the mapper. XMLMapperBuilder parses xml files-"XMLMapperBuilder#cacheElement wants to build-"MapperBuilderAssistant#useNewCache build cache-"MapperBuilderAssistant#addMappedStatement passes in Cache when constructing MappedStatement . In fact, it is not difficult to interpret the mybatis source code.
4. We found that the cache is constructed by mappedStatement, and then we return to how CachingExecutor executes the Cache. Before explaining, we can take a look at the composition of CacheKey (BaseExecutor#createCacheKey). I won't go into details here, but mainly know that the key of Cache is composed of namespace and sql and query conditions. Let's talk about how to get Cache, we return to tcm.getObject(cache, key) in step 2 above. Let's take a look at the methods inside in detail.

private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
private TransactionalCache getTransactionalCache(Cache cache) {
    
    
    return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
  }

It is found that every time Cache is reused, it is managed by TransactionalCacheManager.
5. When the secondary cache cannot be found, execute the executor, such as delegate.query in 2 above.
6. When executing a query, execute BaseExecutor#query, let's take a look at the sql query method:

@SuppressWarnings("unchecked")
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    
    
    // 省略
    List<E> list;
    try {
    
    
      queryStack++;
      // 发现这里是查询localCache类型,若查不到才去数据库中查询,new PerpetualCache("LocalOutputParameterCache")
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
    
    
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
    
    
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
    
    
      queryStack--;
    }
    if (queryStack == 0) {
    
    
      for (DeferredLoad deferredLoad : deferredLoads) {
    
    
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      // 缓存对象,判断是否是STATEMENT,若是,则清空LocalCache
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
    
    
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

The next truth is that the so-called second-level cache is an additional external cache, and the actual first-level cache also exists; first determine whether the second-level cache is enabled, if it is not enabled, query the localCache, if it is enabled, query the second-level cache and maintain it. At that time, query the first-level cache, and query the database if the first-level cache cannot be found.

Cache usage

1. By default, the secondary cache is not turned on. If you want to disable the first level cache, you can set localCacheScope=STATEMENT, as follows:

<settings>
   <setting name="localCacheScope" value="STATEMENT"/>
</settings>

2. If you want to specify the sql statement to clear the cache, set useCache="false" and flushCache="true" in the mapper (thread is not safe, not explained in detail here), as follows:

<select id="selectTest" resultMap="userTable" useCache="false" flushCache="true" >
        select * from user
    </select>

The specific principle implementation code flushCache such as CachingExecutor#query will refresh the cache
3. Call EhcacheCache cache
<cache type=“org.mybatis.caches.ehcache.EhcacheCache”/>

Summary & reflection

  1. The concept of cache and the concept of first-level cache and second-level cache
  2. Cache usage scenarios and considerations
  3. The idea of ​​interpreting the source code, if there is a better way, please advise
  4. Understanding of several key classes and interfaces: DefaultSqlSessionFactory, Configuration, CachingExecutor, MappedStatement, MapperBuilderAssistant, XMLMapperBuilder, Cache, CacheKey

Combined with the previous article explaining mybatis-plugin, do you think mybatis is a very simple framework? Hahaha, I feel that there is only one method. How to use the method is the difficult point. I use it skillfully and skillfully. When encountering the corresponding technical points, consider this similar problem. In fact, mybatis is a semi-ORM framework, because sql still exists, and mybatis is powerful. The point lies in the design concept of powerful dynamic SQL and dynamic proxy mode. Next, let me discuss how to build a distributed architecture concept! Please wait! finished

Scattered attention to recitation point records :

数据库特点:
1、数据结构化 2、数据的共享性高,冗余低,易扩展 3、数据独立性高 4、统一管理和控制
事务的特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)
概念性理解:脏读、不可重复读、幻读

top指令:rownum和rowid、limit


NOT NULL
UNIQUE
PRIMARY KEY
FOREIGN KEY
CHECK
DEFAULT

-- 创建唯一索引
CREATE UNIQUE INDEX uk_users_name ON t_users(name); 
-- 删除唯一索引
drop index uk_users_name;
-- 创建唯一键约束
ALTER TABLE t_users ADD CONSTRAINT uk_users_name1 UNIQUE (NAME);
ALTER TABLE Orders ADD CONSTRAINT fk_PerOrders FOREIGN KEY (Id_P) REFERENCES Persons(Id_P)

alter table dept2 add primary key(dname);
drop index index_name;


 -- 创建序列  Student_stuId_Seq --
create sequence mySeq    :序列名
start with 1          :序列开始值
increment by 1                   :序列每次自增1
maxvalue 3                        :序列最大值
minvalue 1                         :序列最小值
cache 2                              :每次缓存的数值个数
cycle;                                 :是否循环[cycle:循环   |   nocycle: 不循环]


JdbcTransactionFactory   TransactionIsolationLevel(NONE,Read_uncommit,Read_commit,Reapeatable_read,serializable)
BaseExecutor事务管理JdbcTransaction实现Proxy动态代理(InvocationHandler)

1、扫描xml或者注解文件,
properties
settings(defaultExecutorType,logImpl)
typeAliases
Plugins(interceptor并实力话)
objectFactory
objectWrapperFactory
reflectorFactory
environments(创建TransactionFactory,datasource)
databaseIdProvider
typeHandlers
mappers
2、创建SqlSessionFactory
3、openSession打开sql缓存执行器时 DefaultSqlSessionFactory
	1、创建事务工厂(TransationFactory)
	2、ManagedTransaction获取事务管理器,datasource和connection
	3、获取数据库执行类ExecutorType判断,SimpleExecutor、ReuseExecutor、BatchExecutor还是
	CachingExecutor(cacheEnabled)这里默认开启一级缓存,同一个sqlSession多次时只执行一次。
	4、创建DefaultSqlSession
DefaultSqlSession-》CachingExecutor-》BaseExecutor-》 flushCacheIfRequired(ms)

4、创建statementMap,查看是否有sql一样,如果有看是否关闭,若关闭,则jdbcTransaction中通过datasource获取连接
这里jdbcTransation管理事务

typeAlias 类型声明,简写之类
TypeHandler类型转换,jdbc里类型转换成java类型数据。
defaultObjectFactory主要构造bean,返回结果集反射之类的和xml解析

Plugin是居于代理模式启动interceptors,具体流程如下:
	1、configuration#addInterceptor解析文件存放所有拦截器,configuration#newExecutor调用代理
	2、interceptorChain.pluginAll启动代理,返回Executor
	3、interceptor.plugin调用Plugin.wrap,plugin创建InvocationHandler代理对象,封装的所有方法拦截器
	在对于执行类的所有方法Map<Class,Set<Method>>中。
	4、当代理接口类执行时,调用先获取method.getDeclaringClass()对于的声明类,然后取出拦截器的方法(Map<Class,Set<Method>>)
	判断调用方法是否相同if (methods != null && methods.contains(method)),若相同,则执行拦截器
拦截器基本拦截方式:Parameterhandler、ResultSetHandler、StatementHandler、Executor


引用别人面试技术篇:https://www.cnblogs.com/qmillet/p/12523636.html




Guess you like

Origin blog.csdn.net/soft_z1302/article/details/111220101