手写Mybatis:第19章-二级缓存

news2024/11/23 13:05:42

文章目录

  • 一、目标:二级缓存
  • 二、设计:二级缓存
  • 三、实现:二级缓存
    • 3.1 工程结构
    • 3.2 二级缓存类图
    • 3.3 二级缓存队列
      • 3.3.1 FIFI缓存策略
      • 3.3.2 事务缓存
      • 3.3.3 事务管理
      • 3.3.4 修改一级缓存
    • 3.4 缓存执行器
      • 3.4.1 执行器接口
      • 3.4.2 执行器抽象基类
      • 3.4.3 缓存执行器
    • 3.5 修改配置
      • 3.5.1 映射器语句类
      • 3.5.2 配置项
      • 3.5.3 默认SqlSession实现类
    • 3.6 映射构建器助手添加缓存
      • 3.6.1 缓存构建器
      • 3.6.2 映射构建器助手
    • 3.7 XML配置构建器
      • 3.7.1 构建器基类
      • 3.7.2 注解配置构建器
      • 3.7.3 XML语言构建器
      • 3.7.4 XML映射构建器
      • 3.7.5 XML配置构建器-全局缓存解析
      • 3.7.6 XML配置构建器
  • 四、测试:二级缓存
    • 4.1 修改配置
      • 4.1.1 修改xml配置
      • 4.1.2 缓存策略配置
    • 4.2 单元测试
  • 五、总结:二级缓存

一、目标:二级缓存

💡 一级缓存是基于会话,那么怎么在会话结束之后依旧可以使用缓存呢?

  • 关于缓存的实现,希望于当会话结束后,再发起的会话还是相同的查询操作,最好也是可以把数据从缓存中获取出来。
  • 二级缓存:以一个 Mapper 为生命周期,在这个 Mapper 内的同一个操作,无论发起几次会话都可以使用缓存来处理数据。
    • 之所以称为之 二级缓存,是因为它在一级缓存会话层上,添加的额外缓存操作,当会话发生 close、commit 操作时则把数据刷到二级缓存中进行保存,直至执行器发生 update 操作时清空缓存。

二、设计:二级缓存

💡 设计二级缓存?

  • 二级缓存的重点在于无论多少个 SqlSession 会话操作同一个 SQL,不管 SqlSession 是否相同,只要 Mappernamespace 相同就能共享数据。所以二级缓存也被称为 namespace 级别的缓存,相当于一级缓存作用域范围更广了。
  • 设计二级缓存,应该为 Mapper XML 解析后的 MappedStatement 映射器语句提供缓存服务。
  • 当有会话的生命周期结束后,应该将会话的数据刷新到二级缓存中,便于后续在同 namespace 下处理相同 SQL 的操作时使用。

在这里插入图片描述

  • 首先要在 XML 的解析中添加关于全局是否使用缓存的操作,此外因为缓存的作用域范围是在 Mappernamespace 级别上,所以这里要为解析 MappedStatement 映射器语句提供缓存策略。
    • 注意:缓存策略一共有四种实现,包括:LRU、FIFO、SOFT、WEAK
  • 当配置了开启二级缓存服务,那么在开启会话创建执行器时,会把执行器使用缓存执行器做一层装饰器的设计使用。
    • 因为需要通过这个方式将事务缓存起来,同时包装结束会话的指令 close、commit 处理一级缓存数据刷新到二级缓存中。
    • 这样在下次执行相同下 namespace 以及同样的 SQL 时就可以直接从缓存中获取数据了。

三、实现:二级缓存

3.1 工程结构

mybatis-step-18
|-src
  |-main
  | |-java
  |   |-com.lino.mybatis
    |     |-annotations
    |     | |-Delete.java
  |     | |-Insert.java
  |     | |-Select.java
    |     | |-Update.java
    |     |-binding
    |     | |-MapperMethod.java
  |     | |-MapperProxy.java
  |     | |-MapperProxyFactory.java
    |     | |-MapperRegistry.java
    |     |-builder
    |     | |-annotations
    |     | | |-MapperAnnotationBuilder.java
    |     | |-xml
    |     | | |-XMLConfigBuilder.java
    |     | | |-XMLMapperBuilder.java
    |     | | |-XMLStatementBuilder.java
    |     | |-BaseBuilder.java
    |     | |-MapperBuilderAssistant.java
    |     | |-ParameterExpression.java
    |     | |-ResultMapResolver.java
    |     | |-SqlSourceBuilder.java
    |     | |-StaticSqlSource.java
    |     |-cache
    |     | |-decorators
    |     | | |-FifoCache.java
    |     | | |-TransactionalCache.java
    |     | |-impl
    |     | | |-PrepetualCache.java
    |     | |-Cache.java
    |     | |-CacheKey.java
    |     | |-NullCacheKey.java
    |     | |-TransactionalCacheManager.java
  |     |-datasource
  |     | |-druid
  |     | | |-DruidDataSourceFacroty.java
  |     | |-pooled
  |     | | |-PooledConnection.java
  |     | | |-PooledDataSource.java
  |     | | |-PooledDataSourceFacroty.java
  |     | | |-PoolState.java
  |     | |-unpooled
  |     | | |-UnpooledDataSource.java
  |     | | |-UnpooledDataSourceFacroty.java
  |     | |-DataSourceFactory.java
  |     |-executor
  |     | |-keygen
  |     | | |-Jdbc3KeyGenerator.java
  |     | | |-KeyGenerator.java
  |     | | |-NoKeyGenerator.java
  |     | | |-SelectKeyGenerator.java
  |     | |-parameter
  |     | | |-ParameterHandler.java
  |     | |-result
  |     | | |-DefaultResultContext.java
  |     | | |-DefaultResultHandler.java
  |     | |-resultset
  |     | | |-DefaultResultSetHandler.java
  |     | | |-ResultSetHandler.java
  |     | | |-ResultSetWrapper.java
  |     | |-statement
  |     | | |-BaseStatementHandler.java
  |     | | |-PreparedStatementHandler.java
  |     | | |-SimpleStatementHandler.java
  |     | | |-StatementHandler.java
  |     | |-BaseExecutor.java
    |     | |-CachingExecutor.java
    |     | |-ExecutionPlaceholder.java
  |     | |-Executor.java
  |     | |-SimpleExecutor.java
    |     |-io
    |     | |-Resources.java
    |     |-mapping
    |     | |-BoundSql.java
    |     | |-CacheBuilder.java
    |     | |-Environment.java
    |     | |-MappedStatement.java
    |     | |-ParameterMapping.java
    |     | |-ResultFlag.java
    |     | |-ResultMap.java
    |     | |-ResultMapping.java
    |     | |-SqlCommandType.java
    |     | |-SqlSource.java
    |     |-parsing
    |     | |-GenericTokenParser.java
    |     | |-TokenHandler.java
    |     |-plugin
    |     | |-Interceptor.java
    |     | |-InterceptorChain.java
    |     | |-Intercepts.java
    |     | |-Invocation.java
    |     | |-Plugin.java
    |     | |-Signature.java
  |     |-reflection
  |     | |-factory
  |     | | |-DefaultObjectFactory.java
  |     | | |-ObjectFactory.java
  |     | |-invoker
  |     | | |-GetFieldInvoker.java
  |     | | |-Invoker.java
  |     | | |-MethodInvoker.java
  |     | | |-SetFieldInvoker.java
  |     | |-property
  |     | | |-PropertyNamer.java
  |     | | |-PropertyTokenizer.java
  |     | |-wrapper
  |     | | |-BaseWrapper.java
  |     | | |-BeanWrapper.java
  |     | | |-CollectionWrapper.java
  |     | | |-DefaultObjectWrapperFactory.java
  |     | | |-MapWrapper.java
  |     | | |-ObjectWrapper.java
  |     | | |-ObjectWrapperFactory.java
  |     | |-MetaClass.java
  |     | |-MetaObject.java
  |     | |-Reflector.java
  |     | |-SystemMetaObject.java
  |     |-scripting
  |     | |-defaults
  |     | | |-DefaultParameterHandler.java
  |     | | |-RawSqlSource.java
  |     | |-xmltags
  |     | | |-DynamicContext.java
  |     | | |-DynamicSqlSource.java
    |     | | |-ExpressionEvaluator.java
    |     | | |-IfSqlNode.java
  |     | | |-MixedSqlNode.java
    |     | | |-OgnlCache.java
    |     | | |-OgnlClassResolver.java
  |     | | |-SqlNode.java
    |     | | |-StaticTextSqlNode.java
  |     | | |-TextSqlNode.java
    |     | | |-TrimSqlNode.java
  |     | | |-XMLLanguageDriver.java
  |     | | |-XMLScriptBuilder.java
  |     | |-LanguageDriver.java
  |     | |-LanguageDriverRegistry.java
    |     |-session
    |     | |-defaults
    |     | | |-DefaultSqlSession.java
    |     | | |-DefaultSqlSessionFactory.java
    |     | |-Configuration.java
    |     | |-LocalCacheScope.java
    |     | |-ResultContext.java
    |     | |-ResultHandler.java
    |     | |-RowBounds.java
    |     | |-SqlSession.java
    |     | |-SqlSessionFactory.java
    |     | |-SqlSessionFactoryBuilder.java
    |     | |-TransactionIsolationLevel.java
    |     |-transaction
    |     | |-jdbc
    |     | | |-JdbcTransaction.java
    |     | | |-JdbcTransactionFactory.java
    |     | |-Transaction.java
    |     | |-TransactionFactory.java
    |     |-type
    |     | |-BaseTypeHandler.java
    |     | |-DateTypeHandler.java
    |     | |-IntegerTypeHandler.java
    |     | |-JdbcType.java
    |     | |-LongTypeHandler.java
    |     | |-SimpleTypeRegistry.java
    |     | |-StringTypeHandler.java
    |     | |-TypeAliasRegistry.java
    |     | |-TypeHandler.java
    |     | |-TypeHandlerRegistry.java
  |-test
    |-java
    | |-com.lino.mybatis.test
    | |-dao
    | | |-IActivityDao.java
    | |-plugin
    | | |-TestPlugin.java
    | |-po
    | | |-Activity.java
    | |-ApiTest.java
        |-resources
      |-mapper
      | |-Activity_Mapper.xml
          |-mybatis-config-datasource.xml

3.2 二级缓存类图

在这里插入图片描述

  • 整个二级缓存的核心功能逻辑实现,主要以体现在实现 Cache 缓存接口,提供 Mapper XML 解析作用域 namespace 范围的缓存队列的使用上。
  • 通过提供装饰执行器实现模式的 CacheingExecutor 二级缓存执行器,包括会话事务缓存操作,在当会话以 close、commit 方式结束时,将缓存刷新到二级缓存队列中,便于下次相同作用域范围下的同一个查询,可以直接从二级缓存中获取数据。

3.3 二级缓存队列

  • Mybatis 对二级缓存的设计非常灵活,你可以通过配置对缓存的策略做一系列的调整,包括缓存策略:LRU、FIFO、SOFT、WEAK
    • LRU:最近很少使用,主动移除最长时间不被使用的缓存对象。LRU 也是默认的缓存策略。
    • FIFO:先进先出,按对象进入缓存的顺序移除过期对象。
    • SOFT:软引用,基于垃圾回收器状态和软引用规则移除对象。
    • WEAK:弱引用,更主动的基于垃圾回收器状态和弱引用规则移除对象。
  • 同时也提供了相应的数据刷新策略、对象存储限制等。
  • 除此之外,Mybatis 也支持用户自己实现以及跟第三方内存缓存库做集成使用。

3.3.1 FIFI缓存策略

FifoCache.java

package com.lino.mybatis.cache.decorators;

import com.lino.mybatis.cache.Cache;
import java.util.Deque;
import java.util.LinkedList;

/**
 * @description: FIFI(first in, first out) cache decorator
 */
public class FifoCache implements Cache {

    private final Cache delegate;
    private Deque<Object> keyList;
    private int size;

    public FifoCache(Cache delegate) {
        this.delegate = delegate;
        this.keyList = new LinkedList<>();
        this.size = 1024;
    }

    @Override
    public String getId() {
        return delegate.getId();
    }

    @Override
    public void putObject(Object key, Object value) {
        cycleKeyList(key);
        delegate.putObject(key, value);
    }

    @Override
    public Object getObject(Object key) {
        return delegate.getObject(key);
    }

    @Override
    public Object removeObject(Object key) {
        return delegate.removeObject(key);
    }

    @Override
    public void clear() {
        delegate.clear();
        keyList.clear();
    }

    @Override
    public int getSize() {
        return delegate.getSize();
    }

    public void setSize(int size) {
        this.size = size;
    }

    private void cycleKeyList(Object key) {
        keyList.addLast(key);
        if (keyList.size() > size) {
            Object oldestKey = keyList.removeFirst();
            delegate.removeObject(oldestKey);
        }
    }
}
  • FIFO 先进先出队列,基于 Deque 维护了一个链表,其他的操作都包装给 Cache 去完成,属于典型的装饰器模式。
  • FifoCache 所提供的方法实现比较简单,主要包括:存放、获取、移除、清空队列。
  • 另外 cycleKeyList 方法的作用是在增加记录时判断记录是否超过 size 值,以此移除链表的第一个元素,从而达到 FIFO 缓存效果。

3.3.2 事务缓存

  • TransactionalCache 所保存的是会话期间内的缓存数据,当会话结束后则把缓存刷新到二级缓存中。如果是回滚操作则清空缓存。

TransactionalCache.java

package com.lino.mybatis.cache.decorators;

import com.lino.mybatis.cache.Cache;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @description: 事务缓存
 */
public class TransactionalCache implements Cache {

    private Cache delegate;
    /**
     * commit时要不要清缓存
     */
    private boolean clearOnCommit;
    /**
     * commit 时要添加的元素
     */
    private Map<Object, Object> entriesToAddOnCommit;
    private Set<Object> entriesMissedInCache;

    public TransactionalCache(Cache delegate) {
        // delegate = FifoCache
        this.delegate = delegate;
        // 默认 commit 时不清缓存
        this.clearOnCommit = false;
        this.entriesToAddOnCommit = new HashMap<>();
        this.entriesMissedInCache = new HashSet<>();
    }

    @Override
    public String getId() {
        return delegate.getId();
    }

    @Override
    public void putObject(Object key, Object value) {
        entriesToAddOnCommit.put(key, value);
    }

    @Override
    public Object getObject(Object key) {
        // key: CacheKey 拼装后的哈希码
        Object object = delegate.getObject(key);
        if (object == null) {
            entriesMissedInCache.add(key);
        }
        return clearOnCommit ? null : object;
    }

    @Override
    public Object removeObject(Object key) {
        return null;
    }

    @Override
    public void clear() {
        clearOnCommit = true;
        entriesToAddOnCommit.clear();
    }

    @Override
    public int getSize() {
        return delegate.getSize();
    }

    public void commit() {
        if (clearOnCommit) {
            delegate.clear();
        }
        flushPendingEntries();
        reset();
    }

    public void rollback() {
        unlockMissedEntries();
        reset();
    }

    public void reset() {
        clearOnCommit = false;
        entriesToAddOnCommit.clear();
        entriesMissedInCache.clear();
    }

    /**
     * 刷新数据到 MappedStatement#Cache 中,也就是把数据填充到 Mapper XML 级别下。
     * flushPendingEntries 方法把事务缓存下的数据,填充到 FifoCache 中。
     */
    private void flushPendingEntries() {
        for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
            delegate.putObject(entry.getKey(), entry.getValue());
        }
        for (Object entry : entriesMissedInCache) {
            if (!entriesToAddOnCommit.containsKey(entry)) {
                delegate.putObject(entry, null);
            }
        }
    }

    private void unlockMissedEntries() {
        for (Object entry : entriesMissedInCache) {
            delegate.putObject(entry, null);
        }
    }
}
  • TransactionalCache 事务缓存提供了对一级缓存的数据存放和使用的操作。
    • 当一级缓存作用域范围的会话因为 commit、close 结束,则会调用到 flushPengdingEntries 方法。
    • 通过循环处理调用 delegate.putObject(entry.getKey(), entry.getValue()),把数据刷新到二级缓存队列中。
    • 另外 rollback 回滚方法则是一种清空缓存操作。

3.3.3 事务管理

TransactionalCacheManager

package com.lino.mybatis.cache;

import com.lino.mybatis.cache.decorators.TransactionalCache;
import java.util.HashMap;
import java.util.Map;

/**
 * @description: 事务缓存管理器
 */
public class TransactionalCacheManager {

    private Map<Cache, TransactionalCache> transactionCaches = new HashMap<>(16);

    public void clear(Cache cache) {
        getTransactionalCache(cache).clear();
    }

    /**
     * 得到某个TransactionalCache的值
     */
    public Object getObject(Cache cache, CacheKey key) {
        return getTransactionalCache(cache).getObject(key);
    }

    public void putObject(Cache cache, CacheKey key, Object value) {
        getTransactionalCache(cache).putObject(key, value);
    }

    /**
     * 提交时全部提交
     */
    public void commit() {
        for (TransactionalCache txCache : transactionCaches.values()) {
            txCache.commit();
        }
    }

    /**
     * 回滚时全部回滚
     */
    public void rollback() {
        for (TransactionalCache txCache : transactionCaches.values()) {
            txCache.rollback();
        }
    }

    private TransactionalCache getTransactionalCache(Cache cache) {
        TransactionalCache txCache = transactionCaches.get(cache);
        if (txCache == null) {
            txCache = new TransactionalCache(cache);
            transactionCaches.put(cache, txCache);
        }
        return txCache;
    }
}
  • 事务缓存管理器是对事务缓存的包装操作。
  • 用于在缓存执行器创建期间实例化,包装执行期内的所有事务缓存操作,做批量的提交和回滚时缓存数据刷新的处理。

3.3.4 修改一级缓存

PerpetualCache.java

package com.lino.mybatis.cache.impl;

import com.lino.mybatis.cache.Cache;

import java.util.HashMap;
import java.util.Map;

/**
 * @description: 一级缓存,在 Session 生命周期内一直保持,每创建新的 OpenSession 都会创建一个缓存器 PerpetualCache
 */
public class PerpetualCache implements Cache {

    private String id;
    /**
     * 使用HashMap存放一级缓存数据,session 生命周期较短,正常情况下数据不会一直在缓存存放
     */
    private Map<Object, Object> cache = new HashMap<>(16);

    public PerpetualCache(String id) {
        this.id = id;
    }

    @Override
    public String getId() {
        return id;
    }

    @Override
    public void putObject(Object key, Object value) {
        cache.put(key, value);
    }

    @Override
    public Object getObject(Object key) {
        return cache.get(key);
    }

    @Override
    public Object removeObject(Object key) {
        return cache.remove(key);
    }

    @Override
    public void clear() {
        cache.clear();
    }

    @Override
    public int getSize() {
        return cache.size();
    }
}
  • 移除一级缓存日志打印

3.4 缓存执行器

3.4.1 执行器接口

Executor.java

package com.lino.mybatis.executor;

import com.lino.mybatis.cache.CacheKey;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.transaction.Transaction;
import java.sql.SQLException;
import java.util.List;

/**
 * @description: 执行器
 */
public interface Executor {

    /**
     * 结果处理器
     */
    ResultHandler NO_RESULT_HANDLER = null;

    /**
     * 更新
     *
     * @param ms        映射器语句
     * @param parameter 参数
     * @return 返回的是受影响的行数
     * @throws SQLException SQL异常
     */
    int update(MappedStatement ms, Object parameter) throws SQLException;

    /**
     * 查询,含缓存
     *
     * @param ms            映射器语句
     * @param parameter     参数
     * @param rowBounds     分页记录限制
     * @param resultHandler 结果处理器
     * @param key           缓存key
     * @param boundSql      SQL对象
     * @param <E>           返回的类型
     * @return List<E>
     * @throws SQLException SQL异常
     */
    <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException;

    /**
     * 查询
     *
     * @param ms            映射器语句
     * @param parameter     参数
     * @param rowBounds     分页记录限制
     * @param resultHandler 结果处理器
     * @param <E>           返回的类型
     * @return List<E>
     * @throws SQLException SQL异常
     */
    <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

    /**
     * 获取事务
     *
     * @return 事务对象
     */
    Transaction getTransaction();

    /**
     * 提交
     *
     * @param required 是否请求执行
     * @throws SQLException SQL异常
     */
    void commit(boolean required) throws SQLException;

    /**
     * 回滚
     *
     * @param required 是否请求执行
     * @throws SQLException SQL异常
     */
    void rollback(boolean required) throws SQLException;

    /**
     * 关闭
     *
     * @param forceRollback 是否强制回滚
     */
    void close(boolean forceRollback);

    /**
     * 清理session缓存
     */
    void clearLocalCache();

    /**
     * 创建缓存key
     *
     * @param ms              映射器语句
     * @param parameterObject 参数对象
     * @param rowBounds       分页记录限制
     * @param boundSql        SQL对象
     * @return 缓存key
     */
    CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);

    /**
     * 设置执行器
     *
     * @param executor 执行器
     */
    void setExecutorWrapper(Executor executor);
}
  • 添加 setExecutorWrapper 设置执行器方法。

3.4.2 执行器抽象基类

BaseExecutor.java

package com.lino.mybatis.executor;

import com.lino.mybatis.cache.CacheKey;
import com.lino.mybatis.cache.impl.PerpetualCache;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.ParameterMapping;
import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.LocalCacheScope;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.transaction.Transaction;
import com.lino.mybatis.type.TypeHandlerRegistry;
import org.slf4j.LoggerFactory;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

/**
 * @description: 执行器抽象基类
 */
public abstract class BaseExecutor implements Executor {

    ...

    @Override
    public void setExecutorWrapper(Executor executor) {
        this.wrapper = wrapper;
    }

    @Override
    public void close(boolean forceRollback) {
        try {
            try {
                rollback(forceRollback);
            } finally {
                transaction.close();
            }
        } catch (SQLException e) {
            logger.warn("Unexpected exception on closing transaction. Cause: " + e);
        } finally {
            transaction = null;
            localCache = null;
            closed = true;
        }
    }

    ...
}

3.4.3 缓存执行器

  • 缓存执行器是一个装饰器模式,将 SimpleExecutor 做一层包装,提供缓存的能力。
  • 因为这样的包装后就可以将 SimpleExecutor 中的一级缓存以及相应的能力进行使用,在二级缓存 CachingExecutor 执行器中完成缓存在会话周期内的流转操作。

CachingExecutor.java

package com.lino.mybatis.executor;

import com.alibaba.fastjson.JSON;
import com.lino.mybatis.cache.Cache;
import com.lino.mybatis.cache.CacheKey;
import com.lino.mybatis.cache.TransactionalCacheManager;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.transaction.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.SQLException;
import java.util.List;

/**
 * @description: 二级缓存执行器
 */
public class CachingExecutor implements Executor {

    private Logger logger = LoggerFactory.getLogger(BaseExecutor.class);

    private Executor delegate;
    private TransactionalCacheManager tcm = new TransactionalCacheManager();

    public CachingExecutor(Executor delegate) {
        this.delegate = delegate;
        delegate.setExecutorWrapper(this);
    }

    @Override
    public int update(MappedStatement ms, Object parameter) throws SQLException {
        return delegate.update(ms, parameter);
    }

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        Cache cache = ms.getCache();
        if (cache != null) {
            flushCacheIfRequired(ms);
            if (ms.isUseCache() && resultHandler == null) {
                @SuppressWarnings("unchecked")
                List<E> list = (List<E>) tcm.getObject(cache, key);
                if (list == null) {
                    list = delegate.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
                    // cache:缓存队列实现类,FIFO
                    // key:哈希值 [mappedStatementId + offset + limit + SQL + queryParams + environment]
                    // list:查询的数据
                    tcm.putObject(cache, key, list);
                }
                // 打印调试日志,记录二级缓存获取数据
                if (logger.isDebugEnabled() && cache.getSize() > 0) {
                    logger.debug("二级缓存: {}", JSON.toJSONString(list));
                }
                return list;
            }
        }
        return delegate.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        // 1.获取绑定SQL
        BoundSql boundSql = ms.getBoundSql(parameter);
        // 2.创建缓存key
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }

    @Override
    public Transaction getTransaction() {
        return delegate.getTransaction();
    }

    @Override
    public void commit(boolean required) throws SQLException {
        delegate.commit(required);
        tcm.commit();
    }

    @Override
    public void rollback(boolean required) throws SQLException {
        try {
            delegate.rollback(required);
        } finally {
            if (required) {
                tcm.rollback();
            }
        }
    }

    @Override
    public void close(boolean forceRollback) {
        try {
            if (forceRollback) {
                tcm.rollback();
            } else {
                tcm.commit();
            }
        } finally {
            delegate.close(forceRollback);
        }
    }

    @Override
    public void clearLocalCache() {
        delegate.clearLocalCache();
    }

    @Override
    public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
        return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
    }

    @Override
    public void setExecutorWrapper(Executor executor) {
        throw new UnsupportedOperationException("This method should not be called");
    }

    private void flushCacheIfRequired(MappedStatement ms) {
        Cache cache = ms.getCache();
        if (cache != null && ms.isFlushCacheRequired()) {
            tcm.clear(cache);
        }
    }
}
  • CachingExecutor 实现类中主要注意的点是会话中数据查询时的缓存使用,在 query 方法中执行的 delegate.<E>query 操作。
  • 其实这个 delegate 就是 SimpleExecutor 实例化的对象,当缓存数据随着会话周期处理完后,则存放到 MappedStatement 所提供的 Cache 缓存队列中,也就是 FifoCache 先进先出缓存实现类。
  • 另外关于缓存的流转会调用 TransactionalCacheManager 事务缓存管理器进行操作,从会话作用域范围,通过会话的结束,刷新提交到二级缓存或者清空处理。

3.5 修改配置

3.5.1 映射器语句类

MappedStatement

package com.lino.mybatis.mapping;

import com.lino.mybatis.cache.Cache;
import com.lino.mybatis.executor.keygen.Jdbc3KeyGenerator;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.executor.keygen.NoKeyGenerator;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import java.util.Collections;
import java.util.List;

/**
 * @description: 映射器语句类
 */
public class MappedStatement {

    private String resource;
    private Configuration configuration;
    private String id;
    private SqlCommandType sqlCommandType;
    private SqlSource sqlSource;
    Class<?> resultType;
    private LanguageDriver lang;
    private List<ResultMap> resultMaps;
    private boolean flushCacheRequired;
    private KeyGenerator keyGenerator;
    private String[] keyProperties;
    private String[] keyColumns;
    private Cache cache;
    private boolean useCache;

    public MappedStatement() {
    }

    /**
     * 获取SQL对象
     *
     * @param parameterObject 参数
     * @return SQL对象
     */
    public BoundSql getBoundSql(Object parameterObject) {
        // 调用 SqlSource#getBoundSql
        return sqlSource.getBoundSql(parameterObject);
    }

    public static class Builder {

        private MappedStatement mappedStatement = new MappedStatement();

        public Builder(Configuration configuration, String id, SqlCommandType sqlCommandType, SqlSource sqlSource, Class<?> resultType) {
            mappedStatement.configuration = configuration;
            mappedStatement.id = id;
            mappedStatement.sqlCommandType = sqlCommandType;
            mappedStatement.sqlSource = sqlSource;
            mappedStatement.resultType = resultType;
            mappedStatement.keyGenerator = configuration.isUseGeneratedKeys()
                    && SqlCommandType.INSERT.equals(sqlCommandType) ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
            mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();
        }

        public MappedStatement build() {
            assert mappedStatement.configuration != null;
            assert mappedStatement.id != null;
            mappedStatement.resultMaps = Collections.unmodifiableList(mappedStatement.resultMaps);
            return mappedStatement;
        }

        public Builder resource(String resource) {
            mappedStatement.resource = resource;
            return this;
        }

        public String id() {
            return mappedStatement.id;
        }

        public Builder resultMaps(List<ResultMap> resultMaps) {
            mappedStatement.resultMaps = resultMaps;
            return this;
        }

        public Builder keyGenerator(KeyGenerator keyGenerator) {
            mappedStatement.keyGenerator = keyGenerator;
            return this;
        }

        public Builder keyProperty(String keyProperty) {
            mappedStatement.keyProperties = delimitedStringToArray(keyProperty);
            return this;
        }

        public Builder cache(Cache cache) {
            mappedStatement.cache = cache;
            return this;
        }

        public Builder flushCacheRequired(boolean flushCacheRequired) {
            mappedStatement.flushCacheRequired = flushCacheRequired;
            return this;
        }

        public Builder useCache(boolean useCache) {
            mappedStatement.useCache = useCache;
            return this;
        }

    }

    private static String[] delimitedStringToArray(String in) {
        if (in == null || in.trim().length() == 0) {
            return null;
        } else {
            return in.split(",");
        }
    }

    public Configuration getConfiguration() {
        return configuration;
    }

    public String getId() {
        return id;
    }

    public SqlCommandType getSqlCommandType() {
        return sqlCommandType;
    }

    public SqlSource getSqlSource() {
        return sqlSource;
    }

    public Class<?> getResultType() {
        return resultType;
    }

    public LanguageDriver getLang() {
        return lang;
    }

    public List<ResultMap> getResultMaps() {
        return resultMaps;
    }

    public String[] getKeyProperties() {
        return keyProperties;
    }

    public KeyGenerator getKeyGenerator() {
        return keyGenerator;
    }

    public String getResource() {
        return resource;
    }

    public String[] getKeyColumns() {
        return keyColumns;
    }

    public boolean isFlushCacheRequired() {
        return flushCacheRequired;
    }

    public Cache getCache() {
        return cache;
    }

    public boolean isUseCache() {
        return useCache;
    }
}
  • 添加 Cache 缓存对象

3.5.2 配置项

Configuration.java

package com.lino.mybatis.session;

import com.lino.mybatis.binding.MapperRegistry;
import com.lino.mybatis.cache.Cache;
import com.lino.mybatis.cache.decorators.FifoCache;
import com.lino.mybatis.cache.impl.PerpetualCache;
import com.lino.mybatis.datasource.druid.DruidDataSourceFactory;
import com.lino.mybatis.datasource.pooled.PooledDataSourceFactory;
import com.lino.mybatis.datasource.unpooled.UnpooledDataSourceFactory;
import com.lino.mybatis.executor.CachingExecutor;
import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.executor.SimpleExecutor;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.executor.parameter.ParameterHandler;
import com.lino.mybatis.executor.resultset.DefaultResultSetHandler;
import com.lino.mybatis.executor.resultset.ResultSetHandler;
import com.lino.mybatis.executor.statement.PreparedStatementHandler;
import com.lino.mybatis.executor.statement.StatementHandler;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.Environment;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.ResultMap;
import com.lino.mybatis.plugin.Interceptor;
import com.lino.mybatis.plugin.InterceptorChain;
import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.reflection.factory.DefaultObjectFactory;
import com.lino.mybatis.reflection.factory.ObjectFactory;
import com.lino.mybatis.reflection.wrapper.DefaultObjectWrapperFactory;
import com.lino.mybatis.reflection.wrapper.ObjectWrapperFactory;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.scripting.LanguageDriverRegistry;
import com.lino.mybatis.scripting.xmltags.XMLLanguageDriver;
import com.lino.mybatis.transaction.Transaction;
import com.lino.mybatis.transaction.jdbc.JdbcTransactionFactory;
import com.lino.mybatis.type.TypeAliasRegistry;
import com.lino.mybatis.type.TypeHandlerRegistry;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @description: 配置项
 * @author: lingjian
 * @createDate: 2022/11/7 21:32
 */
public class Configuration {

    /**
     * 环境
     */
    protected Environment environment;
    /**
     * 是否使用自动生成键值对
     */
    protected boolean useGeneratedKeys = false;
    /**
     * 默认启用缓存,cacheEnabled = true/false
     */
    protected boolean cacheEnabled = true;
    /**
     * 缓存机制,默认不配置的情况是 SESSION
     */
    protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
    /**
     * 映射注册机
     */
    protected MapperRegistry mapperRegistry = new MapperRegistry(this);
    /**
     * 映射的语句,存在Map里
     */
    protected final Map<String, MappedStatement> mappedStatements = new HashMap<>(16);
    /**
     * 缓存,存在Map里
     */
    protected final Map<String, Cache> caches = new HashMap<>(16);
    /**
     * 结果映射,存在Map里
     */
    protected final Map<String, ResultMap> resultMaps = new HashMap<>(16);
    /**
     * 键值生成器,存在Map里
     */
    protected final Map<String, KeyGenerator> keyGenerators = new HashMap<>(16);
    /**
     * 插件拦截器链
     */
    protected final InterceptorChain interceptorChain = new InterceptorChain();
    /**
     * 类型别名注册机
     */
    protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
    /**
     * 脚本语言注册器
     */
    protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
    /**
     * 类型处理器注册机
     */
    protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
    /**
     * 对象工厂
     */
    protected ObjectFactory objectFactory = new DefaultObjectFactory();
    /**
     * 对象包装工厂
     */
    protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
    /**
     * 准备资源列表
     */
    protected final Set<String> loadedResources = new HashSet<>();
    /**
     * 数据库ID
     */
    protected String databaseId;

    public Configuration() {
        typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);

        typeAliasRegistry.registerAlias("DRUID", DruidDataSourceFactory.class);
        typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
        typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);

        typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
        typeAliasRegistry.registerAlias("FIFO", FifoCache.class);

        languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
    }

    public void addMappers(String packageName) {
        mapperRegistry.addMappers(packageName);
    }

    public <T> void addMapper(Class<T> type) {
        mapperRegistry.addMapper(type);
    }

    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return mapperRegistry.getMapper(type, sqlSession);
    }

    public boolean hasMapper(Class<?> type) {
        return mapperRegistry.hasMapper(type);
    }

    public void addMappedStatement(MappedStatement ms) {
        mappedStatements.put(ms.getId(), ms);
    }

    public MappedStatement getMappedStatement(String id) {
        return mappedStatements.get(id);
    }

    public TypeAliasRegistry getTypeAliasRegistry() {
        return typeAliasRegistry;
    }

    public Environment getEnvironment() {
        return environment;
    }

    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    public String getDatabaseId() {
        return databaseId;
    }

    public ObjectFactory getObjectFactory() {
        return objectFactory;
    }

    public boolean isUseGeneratedKeys() {
        return useGeneratedKeys;
    }

    public void setUseGeneratedKeys(boolean useGeneratedKeys) {
        this.useGeneratedKeys = useGeneratedKeys;
    }

    public LocalCacheScope getLocalCacheScope() {
        return localCacheScope;
    }

    public void setLocalCacheScope(LocalCacheScope localCacheScope) {
        this.localCacheScope = localCacheScope;
    }

    /**
     * 生产执行器
     *
     * @param transaction 事务
     * @return 执行器
     */
    public Executor newExecutor(Transaction transaction) {
        Executor executor = new SimpleExecutor(this, transaction);
        // 配置开启缓存,创建 CachingExecutor(默认就是有缓存)装饰着模式
        if (cacheEnabled) {
            executor = new CachingExecutor(executor);
        }
        return executor;
    }

    /**
     * 创建语句处理器
     *
     * @param executor        执行器
     * @param mappedStatement 映射器语句类
     * @param parameter       参数
     * @param rowBounds       分页记录限制
     * @param resultHandler   结果处理器
     * @param boundSql        SQL语句
     * @return StatementHandler 语句处理器
     */
    public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        StatementHandler statementHandler = new PreparedStatementHandler(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
        // 嵌入插件,代理对象
        statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
        return statementHandler;
    }

    /**
     * 创建结果集处理器
     *
     * @param executor        执行器
     * @param mappedStatement 映射器语句类
     * @param boundSql        SQL语句
     * @return ResultSetHandler 结果集处理器
     */
    public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        return new DefaultResultSetHandler(executor, mappedStatement, resultHandler, rowBounds, boundSql);
    }

    /**
     * 创建元对象
     *
     * @param object 原对象
     * @return 元对象
     */
    public MetaObject newMetaObject(Object object) {
        return MetaObject.forObject(object, objectFactory, objectWrapperFactory);
    }

    /**
     * 创建类型处理器注册机
     *
     * @return TypeHandlerRegistry 类型处理器注册机
     */
    public TypeHandlerRegistry getTypeHandlerRegistry() {
        return typeHandlerRegistry;
    }

    /**
     * 是否包含资源
     *
     * @param resource 资源
     * @return 是否
     */
    public boolean isResourceLoaded(String resource) {
        return loadedResources.contains(resource);
    }

    /**
     * 添加资源
     *
     * @param resource 资源
     */
    public void addLoadedResource(String resource) {
        loadedResources.add(resource);
    }

    /**
     * 获取脚本语言注册机
     *
     * @return languageRegistry 脚本语言注册机
     */
    public LanguageDriverRegistry getLanguageRegistry() {
        return languageRegistry;
    }

    /**
     * 获取参数处理器
     *
     * @param mappedStatement 映射器语言类型
     * @param parameterObject 参数对象
     * @param boundSql        SQL语句
     * @return ParameterHandler参数处理器
     */
    public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
        // 创建参数处理器
        ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
        return parameterHandler;
    }

    /**
     * 获取默认脚本语言驱动
     *
     * @return 脚本语言驱动
     */
    public LanguageDriver getDefaultScriptingLanguageInstance() {
        return languageRegistry.getDefaultDriver();
    }

    public ResultMap getResultMap(String id) {
        return resultMaps.get(id);
    }

    public void addResultMap(ResultMap resultMap) {
        resultMaps.put(resultMap.getId(), resultMap);
    }

    public void addKeyGenerator(String id, KeyGenerator keyGenerator) {
        keyGenerators.put(id, keyGenerator);
    }

    public KeyGenerator getKeyGenerator(String id) {
        return keyGenerators.get(id);
    }

    public boolean hasKeyGenerators(String id) {
        return keyGenerators.containsKey(id);
    }

    public void addInterceptor(Interceptor interceptorInstance) {
        interceptorChain.addInterceptor(interceptorInstance);
    }

    public boolean isCacheEnabled() {
        return cacheEnabled;
    }

    public void setCacheEnabled(boolean cacheEnabled) {
        this.cacheEnabled = cacheEnabled;
    }

    public void addCache(Cache cache) {
        caches.put(cache.getId(), cache);
    }

    public Cache getCache(String id) {
        return caches.get(id);
    }

}
  • newExecutor 装饰缓存执行。

3.5.3 默认SqlSession实现类

DefaultSqlSession.java

package com.lino.mybatis.session.defaults;

import com.alibaba.fastjson.JSON;
import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.session.SqlSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.SQLException;
import java.util.List;

/**
 * @description: 默认sqlSession实现类
 */
public class DefaultSqlSession implements SqlSession {

    ...

    @Override
    public void close() {
        executor.close(false);
    }

    ...
}

3.6 映射构建器助手添加缓存

3.6.1 缓存构建器

CacheBuilder.java

package com.lino.mybatis.mapping;

import com.lino.mybatis.cache.Cache;
import com.lino.mybatis.cache.decorators.FifoCache;
import com.lino.mybatis.cache.impl.PerpetualCache;
import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.reflection.SystemMetaObject;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * @description: 缓存构建器, 建造者模式
 */
public class CacheBuilder {

    private String id;
    private Class<? extends Cache> implementation;
    private List<Class<? extends Cache>> decorators;
    private Integer size;
    private Long clearInterval;
    private boolean readWrite;
    private Properties properties;
    private boolean blocking;

    public CacheBuilder(String id) {
        this.id = id;
        this.decorators = new ArrayList<>();
    }

    public CacheBuilder implementation(Class<? extends Cache> implementation) {
        this.implementation = implementation;
        return this;
    }

    public CacheBuilder addDecorator(Class<? extends Cache> decorator) {
        if (decorator != null) {
            this.decorators.add(decorator);
        }
        return this;
    }

    public CacheBuilder size(Integer size) {
        this.size = size;
        return this;
    }

    public CacheBuilder clearInterval(Long clearInterval) {
        this.clearInterval = clearInterval;
        return this;
    }

    public CacheBuilder readWrite(boolean readWrite) {
        this.readWrite = readWrite;
        return this;
    }

    public CacheBuilder blocking(boolean blocking) {
        this.blocking = blocking;
        return this;
    }

    public CacheBuilder properties(Properties properties) {
        this.properties = properties;
        return this;
    }

    public Cache build() {
        setDefaultImplementations();
        Cache cache = newBaseCacheInstance(implementation, id);
        setCacheProperties(cache);
        if (PerpetualCache.class.equals(cache.getClass())) {
            for (Class<? extends Cache> decorator : decorators) {
                // 使用装饰者模式包装
                cache = newCacheDecoratorInstance(decorator, cache);
                // 额外属性设置
                setCacheProperties(cache);
            }
        }
        return cache;
    }

    private void setDefaultImplementations() {
        if (implementation == null) {
            implementation = PerpetualCache.class;
            if (decorators.isEmpty()) {
                decorators.add(FifoCache.class);
            }
        }
    }

    private void setCacheProperties(Cache cache) {
        if (properties != null) {
            MetaObject metaCache = SystemMetaObject.forObject(cache);
            for (Map.Entry<Object, Object> entry : properties.entrySet()) {
                String name = (String) entry.getKey();
                String value = (String) entry.getValue();
                if (metaCache.hasSetter(name)) {
                    Class<?> type = metaCache.getSetterType(name);
                    if (String.class == type) {
                        metaCache.setValue(name, value);
                    } else if (int.class == type || Integer.class == type) {
                        metaCache.setValue(name, Integer.valueOf(value));
                    } else if (long.class == type || Long.class == type) {
                        metaCache.setValue(name, Long.valueOf(value));
                    } else if (short.class == type || Short.class == type) {
                        metaCache.setValue(name, Short.valueOf(value));
                    } else if (byte.class == type || Byte.class == type) {
                        metaCache.setValue(name, Byte.valueOf(value));
                    } else if (float.class == type || Float.class == type) {
                        metaCache.setValue(name, Float.valueOf(value));
                    } else if (boolean.class == type || Boolean.class == type) {
                        metaCache.setValue(name, Boolean.valueOf(value));
                    } else if (double.class == type || Double.class == type) {
                        metaCache.setValue(name, Double.valueOf(value));
                    } else {
                        throw new RuntimeException("Unsupported property type for cache: '" + name + "' of type " + type);
                    }
                }
            }
        }
    }

    private Cache newBaseCacheInstance(Class<? extends Cache> cacheClass, String id) {
        Constructor<? extends Cache> cacheConstructor = getBaseCacheConstructor(cacheClass);
        try {
            return cacheConstructor.newInstance(id);
        } catch (Exception e) {
            throw new RuntimeException("Could not instantiate cache implementation (" + cacheClass + "). Cause: " + e, e);
        }
    }

    private Constructor<? extends Cache> getBaseCacheConstructor(Class<? extends Cache> cacheClass) {
        try {
            return cacheClass.getConstructor(String.class);
        } catch (Exception e) {
            throw new RuntimeException("Invalid base cache implementation (" + cacheClass + ").  " +
                    "Base cache implementations must have a constructor that takes a String id as a parameter.  Cause: " + e, e);
        }
    }

    private Cache newCacheDecoratorInstance(Class<? extends Cache> cacheClass, Cache base) {
        Constructor<? extends Cache> cacheConstructor = getCacheDecoratorConstructor(cacheClass);
        try {
            return cacheConstructor.newInstance(base);
        } catch (Exception e) {
            throw new RuntimeException("Could not instantiate cache decorator (" + cacheClass + "). Cause: " + e, e);
        }
    }

    private Constructor<? extends Cache> getCacheDecoratorConstructor(Class<? extends Cache> cacheClass) {
        try {
            return cacheClass.getConstructor(Cache.class);
        } catch (Exception e) {
            throw new RuntimeException("Invalid cache decorator (" + cacheClass + ").  " +
                    "Cache decorators must have a constructor that takes a Cache instance as a parameter.  Cause: " + e, e);
        }
    }
}

3.6.2 映射构建器助手

MapperBuilderAssistant.java

package com.lino.mybatis.builder;

import com.lino.mybatis.cache.Cache;
import com.lino.mybatis.cache.decorators.FifoCache;
import com.lino.mybatis.cache.impl.PerpetualCache;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.mapping.*;
import com.lino.mybatis.reflection.MetaClass;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.type.TypeHandler;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * @description: 映射构建器助手,建造者
 */
public class MapperBuilderAssistant extends BaseBuilder {

    private String currentNamespace;
    private String resource;
    private Cache currentCache;

    public MapperBuilderAssistant(Configuration configuration, String resource) {
        super(configuration);
        this.resource = resource;
    }

    public ResultMapping buildResultMapping(Class<?> resultType, String property, String column, List<ResultFlag> flags) {
        Class<?> javaTypeClass = resolveResultJavaType(resultType, property, null);
        TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, null);

        ResultMapping.Builder builder = new ResultMapping.Builder(configuration, property, column, javaTypeClass);
        builder.typeHandler(typeHandlerInstance);
        builder.flags(flags);

        return builder.build();
    }

    private Class<?> resolveResultJavaType(Class<?> resultType, String property, Class<?> javaType) {
        if (javaType == null && property != null) {
            try {
                MetaClass metaResultType = MetaClass.forClass(resultType);
                javaType = metaResultType.getSetterType(property);
            } catch (Exception ignore) {

            }
        }
        if (javaType == null) {
            javaType = Object.class;
        }
        return javaType;
    }

    public String getCurrentNamespace() {
        return currentNamespace;
    }

    public void setCurrentNamespace(String currentNamespace) {
        this.currentNamespace = currentNamespace;
    }

    public String applyCurrentNamespace(String base, boolean isReference) {
        if (base == null) {
            return null;
        }
        if (isReference) {
            if (base.contains(".")) {
                return base;
            }
        } else {
            if (base.startsWith(currentNamespace + ".")) {
                return base;
            }
            if (base.contains(".")) {
                throw new RuntimeException("Dots are not allowed in element names, please remove it from " + base);
            }
        }
        return currentNamespace + "." + base;
    }

    /**
     * 添加映射器语句
     */
    public MappedStatement addMappedStatement(String id, SqlSource sqlSource, SqlCommandType sqlCommandType,
                                              Class<?> parameterType, String resultMap, Class<?> resultType,
                                              boolean flushCache, boolean useCache,
                                              KeyGenerator keyGenerator, String keyProperty, LanguageDriver lang) {
        // 给id加上namespace前缀:com.lino.mybatis.test.dao.IUserDao.queryUserInfoById
        id = applyCurrentNamespace(id, false);
        // 是否时select语句
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

        MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlCommandType, sqlSource, resultType);
        statementBuilder.resource(resource);
        statementBuilder.keyGenerator(keyGenerator);
        statementBuilder.keyProperty(keyProperty);

        // 结果映射, 给 MappedStatement#resultMaps
        setStatementResultMap(resultMap, resultType, statementBuilder);
        setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder);

        MappedStatement statement = statementBuilder.build();
        // 映射语句信息,建造完存放到配置项中
        configuration.addMappedStatement(statement);

        return statement;
    }

    private void setStatementCache(boolean isSelect, boolean flushCache, boolean useCache, Cache cache, MappedStatement.Builder statementBuilder) {
        flushCache = valueOrDefault(flushCache, !isSelect);
        useCache = valueOrDefault(useCache, !isSelect);
        statementBuilder.flushCacheRequired(flushCache);
        statementBuilder.useCache(useCache);
        statementBuilder.cache(cache);
    }

    private void setStatementResultMap(String resultMap, Class<?> resultType, MappedStatement.Builder statementBuilder) {
        // 因为暂时还没有在 Mapper XML 中配置 Map 返回结果,所以这里返回的是 null
        resultMap = applyCurrentNamespace(resultMap, true);

        List<ResultMap> resultMaps = new ArrayList<>();

        if (resultMap != null) {
            String[] resultMapNames = resultMap.split(",");
            for (String resultMapName : resultMapNames) {
                resultMaps.add(configuration.getResultMap(resultMapName.trim()));
            }
        }
        /*
         * 通常使用 resultType 即可满足大部分场景
         * <select id="queryUserInfoById" resultType="com.lino.mybatis.test.po.User">
         * 使用 resultType 的情况下,Mybatis 会自动创建一个 ResultMap,基于属性名称映射列到 JavaBean 的属性上。
         */
        else if (resultType != null) {
            ResultMap.Builder inlineResultMapBuilder = new ResultMap.Builder(
                    configuration, statementBuilder.id() + "-Inline", resultType, new ArrayList<>());
            resultMaps.add(inlineResultMapBuilder.build());
        }
        statementBuilder.resultMaps(resultMaps);
    }

    public ResultMap addResultMap(String id, Class<?> type, List<ResultMapping> resultMappings) {
        // 补全ID全路径,如:com.lino.mybatis.test.dao.IActivityDao + activityMap
        id = applyCurrentNamespace(id, false);

        ResultMap.Builder inlineResultMapBuilder = new ResultMap.Builder(configuration, id, type, resultMappings);

        ResultMap resultMap = inlineResultMapBuilder.build();
        configuration.addResultMap(resultMap);
        return resultMap;
    }

    public Cache useNewCache(Class<? extends Cache> typeClass,
                             Class<? extends Cache> evictionClass,
                             Long flushInterval,
                             Integer size,
                             boolean readWrite,
                             boolean blocking,
                             Properties props) {
        // 判断为null,则用默认值
        typeClass = valueOrDefault(typeClass, PerpetualCache.class);
        evictionClass = valueOrDefault(evictionClass, FifoCache.class);

        // 建造者模式构建 cache [currentNamespace=com.lino.mybatis.test.dao.IActivityDao]
        Cache cache = new CacheBuilder(currentNamespace)
                .implementation(typeClass)
                .addDecorator(evictionClass)
                .clearInterval(flushInterval)
                .size(size)
                .readWrite(readWrite)
                .blocking(blocking)
                .properties(props)
                .build();

        // 添加缓存
        configuration.addCache(cache);
        currentCache = cache;
        return cache;
    }

    private <T> T valueOrDefault(T value, T defaultValue) {
        return value == null ? defaultValue : value;
    }
}

3.7 XML配置构建器

3.7.1 构建器基类

BaseBuilder.java

package com.lino.mybatis.builder;

import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.type.TypeAliasRegistry;
import com.lino.mybatis.type.TypeHandler;
import com.lino.mybatis.type.TypeHandlerRegistry;

/**
 * @description: 构建器的基类,建造者模式
 */
public class BaseBuilder {

    protected final Configuration configuration;
    protected final TypeAliasRegistry typeAliasRegistry;
    protected final TypeHandlerRegistry typeHandlerRegistry;

    ...

    protected Boolean booleanValueOf(String value, Boolean defaultValue) {
        return value == null ? defaultValue : Boolean.valueOf(value);
    }
}

3.7.2 注解配置构建器

MapperAnnotationBuilder.java

package com.lino.mybatis.builder.annotation;

import com.lino.mybatis.annotations.Delete;
import com.lino.mybatis.annotations.Insert;
import com.lino.mybatis.annotations.Select;
import com.lino.mybatis.annotations.Update;
import com.lino.mybatis.binding.MapperMethod;
import com.lino.mybatis.builder.MapperBuilderAssistant;
import com.lino.mybatis.executor.keygen.Jdbc3KeyGenerator;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.executor.keygen.NoKeyGenerator;
import com.lino.mybatis.mapping.SqlCommandType;
import com.lino.mybatis.mapping.SqlSource;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;

/**
 * @description: 注解配置构建器 Mapper
 */
public class MapperAnnotationBuilder {

    ...

    /**
     * 解析语句
     *
     * @param method 方法
     */
    private void parseStatement(Method method) {
        Class<?> parameterTypeClass = getParameterType(method);
        LanguageDriver languageDriver = getLanguageDriver(method);
        SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);

        if (sqlSource != null) {
            final String mappedStatementId = type.getName() + "." + method.getName();
            SqlCommandType sqlCommandType = getSqlCommandType(method);

            KeyGenerator keyGenerator;
            String keyProperty = "id";
            if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
                keyGenerator = configuration.isUseGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
            } else {
                keyGenerator = new NoKeyGenerator();
            }

            boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

            String resultMapId = null;
            if (isSelect) {
                resultMapId = parseResultMap(method);
            }

            // 调用助手类
            assistant.addMappedStatement(
                    mappedStatementId,
                    sqlSource,
                    sqlCommandType,
                    parameterTypeClass,
                    resultMapId,
                    getReturnType(method),
                    false,
                    false,
                    keyGenerator,
                    keyProperty,
                    languageDriver
            );
        }
    }

    ...

}

3.7.3 XML语言构建器

XMLStatementBuilder.java

package com.lino.mybatis.builder.xml;

import com.lino.mybatis.builder.BaseBuilder;
import com.lino.mybatis.builder.MapperBuilderAssistant;
import com.lino.mybatis.executor.keygen.Jdbc3KeyGenerator;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.executor.keygen.NoKeyGenerator;
import com.lino.mybatis.executor.keygen.SelectKeyGenerator;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.SqlCommandType;
import com.lino.mybatis.mapping.SqlSource;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import org.dom4j.Element;
import java.util.List;
import java.util.Locale;

/**
 * @description: XML语言构建器
 */
public class XMLStatementBuilder extends BaseBuilder {

    ...

    /**
     * 解析语句(select|insert|update|delete)
     * <select
     * id="selectPerson"
     * parameterType="int"
     * parameterMap="deprecated"
     * resultType="hashmap"
     * resultMap="personResultMap"
     * flushCache="false"
     * useCache="true"
     * timeout="10000"
     * fetchSize="256"
     * statementType="PREPARED"
     * resultSetType="FORWARD_ONLY">
     * SELECT * FROM PERSON WHERE ID = #{id}
     * </select>
     */
    public void parseStatementNode() {
        String id = element.attributeValue("id");
        // 参数类型
        String parameterType = element.attributeValue("parameterType");
        Class<?> parameterTypeClass = resolveAlias(parameterType);
        // 外部应用 resultMap
        String resultMap = element.attributeValue("resultMap");
        // 结果类型
        String resultType = element.attributeValue("resultType");
        Class<?> resultTypeClass = resolveAlias(resultType);
        // 获取命令类型(select|insert|update|delete)
        String nodeName = element.getName();
        SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));

        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        boolean flushCache = Boolean.parseBoolean(element.attributeValue("flushCache", String.valueOf(!isSelect)));
        boolean useCache = Boolean.parseBoolean(element.attributeValue("useCache", String.valueOf(isSelect)));

        // 获取默认语言驱动器
        Class<?> langClass = configuration.getLanguageRegistry().getDefaultDriverClass();
        LanguageDriver langDriver = configuration.getLanguageRegistry().getDriver(langClass);

        // 解析<selectKey>
        processSelectKeyNodes(id, parameterTypeClass, langDriver);

        // 解析成SqlSource,DynamicSqlSource/RawSqlSource
        SqlSource sqlSource = langDriver.createSqlSource(configuration, element, parameterTypeClass);

        // 属性标记【仅对insert有用】,MyBatis 会通过 getGeneratedKeys 或者通过 insert 语句的 selectKey 子元素设置它的值
        String keyProperty = element.attributeValue("keyProperty");

        KeyGenerator keyGenerator = null;
        String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
        keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);

        if (configuration.hasKeyGenerators(keyStatementId)) {
            keyGenerator = configuration.getKeyGenerator(keyStatementId);
        } else {
            keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? new Jdbc3KeyGenerator() :
                    new NoKeyGenerator();
        }

        // 调用助手类
        builderAssistant.addMappedStatement(id,
                sqlSource,
                sqlCommandType,
                parameterTypeClass,
                resultMap,
                resultTypeClass,
                flushCache,
                useCache,
                keyGenerator,
                keyProperty,
                langDriver);
    }

    ...

    /**
     * <selectKey keyProperty="id" order="AFTER" resultType="long">
     * SELECT LAST_INSERT_ID()
     * </selectKey>
     */
    private void parseSelectKeyNode(String id, Element nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver) {
        String resultType = nodeToHandle.attributeValue("resultType");
        Class<?> resultTypeClass = resolveClass(resultType);
        boolean executeBefore = "BEFORE".equals(nodeToHandle.attributeValue("order", "AFTER"));
        String keyProperty = nodeToHandle.attributeValue("keyProperty");

        // 默认
        String resultMap = null;
        boolean flushCache = false;
        boolean useCache = false;
        KeyGenerator keyGenerator = new NoKeyGenerator();

        // 解析成SqlSource,DynamicSqlSource/RawSqlSource
        SqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);
        SqlCommandType sqlCommandType = SqlCommandType.SELECT;

        // 调用助手类
        builderAssistant.addMappedStatement(id,
                sqlSource,
                sqlCommandType,
                parameterTypeClass,
                resultMap,
                resultTypeClass,
                flushCache,
                useCache,
                keyGenerator,
                keyProperty,
                langDriver);

        // 给id加上namespace前缀
        id = builderAssistant.applyCurrentNamespace(id, false);

        // 存放键值生成器配置
        MappedStatement keyStatement = configuration.getMappedStatement(id);
        configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));
    }

}

3.7.4 XML映射构建器

XMLMapperBuilder.java

package com.lino.mybatis.builder.xml;

import com.lino.mybatis.builder.BaseBuilder;
import com.lino.mybatis.builder.MapperBuilderAssistant;
import com.lino.mybatis.builder.ResultMapResolver;
import com.lino.mybatis.cache.Cache;
import com.lino.mybatis.io.Resources;
import com.lino.mybatis.mapping.ResultFlag;
import com.lino.mybatis.mapping.ResultMap;
import com.lino.mybatis.mapping.ResultMapping;
import com.lino.mybatis.session.Configuration;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;

/**
 * @description: XML映射构建器
 */
public class XMLMapperBuilder extends BaseBuilder {

    ...

    /**
     * 配置mapper元素
     * <mapper namespace="org.mybatis.example.BlogMapper">
     * <select id="selectBlog" parameterType="int" resultType="Blog">
     * select * from Blog where id = #{id}
     * </select>
     * </mapper>
     *
     * @param element 元素
     */
    private void configurationElement(Element element) {
        // 1.配置namespace
        String namespace = element.attributeValue("namespace");
        if ("".equals(namespace)) {
            throw new RuntimeException("Mapper's namespace cannot be empty");
        }
        builderAssistant.setCurrentNamespace(namespace);

        // 2.配置cache
        cacheElement(element.element("cache"));

        // 3.解析resultMap
        resultMapElement(element.elements("resultMap"));

        // 4.配置select|insert|update|delete
        buildStatementFromContext(element.elements("select"), element.elements("insert"), element.elements("update"), element.elements("delete"));
    }

    /**
     * <cache eviction="FIFO" flushInterval="600000" size="512" readOnly="true"/>
     */
    private void cacheElement(Element context) {
        if (context == null) {
            return;
        }
        // 基础配置信息
        String type = context.attributeValue("type", "PERPETUAL");
        Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
        // 缓存队列 FIFO
        String eviction = context.attributeValue("eviction", "FIFO");
        Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
        Long flushInterval = Long.valueOf(context.attributeValue("flushInterval"));
        Integer size = Integer.valueOf(context.attributeValue("size"));
        boolean readWrite = !Boolean.parseBoolean(context.attributeValue("readOnly", "false"));
        boolean blocking = !Boolean.parseBoolean(context.attributeValue("blocking", "false"));

        // 解析额外属性信息:<property name="cacheFile" value="/tmp/xxx-cache.tmp"/>
        List<Element> elements = context.elements();
        Properties props = new Properties();
        for (Element element : elements) {
            props.setProperty(element.attributeValue("name"), element.attributeValue("value"));
        }
        // 构建缓存
        builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }

    ...
}
  • cacheElement 缓存标签的解析作用于 configurationElement 解析环节中。解析后创建 Cache 存放到 Configuration 配置项对应的属性值。
  • 同时创建出来的缓存会被记录到 MappedStatement 映射语句类的属性上,便于在缓存执行器中使用。

3.7.5 XML配置构建器-全局缓存解析

<settings>
    <!--全局缓存:true/false-->
    <setting name="cacheEnabled" value="true"/>
    <!--缓存级别:SESSION/STATEMENT-->
    <setting name="localCacheScope" value="STATEMENT"/>
</settings>
  • Config XML 中配置全局缓存为开启,这个时候可以关闭一级缓存。

3.7.6 XML配置构建器

XMLConfigBuilder.java

package com.lino.mybatis.builder.xml;

import com.lino.mybatis.builder.BaseBuilder;
import com.lino.mybatis.datasource.DataSourceFactory;
import com.lino.mybatis.io.Resources;
import com.lino.mybatis.mapping.Environment;
import com.lino.mybatis.plugin.Interceptor;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.LocalCacheScope;
import com.lino.mybatis.transaction.TransactionFactory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.xml.sax.InputSource;
import javax.sql.DataSource;
import java.io.InputStream;
import java.io.Reader;
import java.util.List;
import java.util.Properties;

/**
 * @description: XML配置构建器,建造者模式,集成BaseBuilder
 */
public class XMLConfigBuilder extends BaseBuilder {

    ...

    /**
     * <settings>
     * <!--缓存级别:SESSION/STATEMENT-->
     * <setting name="localCacheScope" value="SESSION"/>
     * </settings>
     */
    private void settingElement(Element context) {
        if (context == null) {
            return;
        }
        List<Element> elements = context.elements();
        Properties props = new Properties();
        for (Element element : elements) {
            props.setProperty(element.attributeValue("name"), element.attributeValue("value"));
        }
        configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
        configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope")));
    }

    ...

}
  • 解析全局配置的操作,再结合 XMLConfigBuilder 配置构建器中对配置解析扩展,添加 cacheEnabled 即可。
  • 这样就可以把是否开启二级缓存的操作保存配置项目。
  • 默认情况下,二级缓存是关闭的。

四、测试:二级缓存

4.1 修改配置

4.1.1 修改xml配置

mybatis-config-datasource.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    ...

    <settings>
        <!--全局缓存:true/false-->
        <setting name="cacheEnabled" value="true"/>
        <!--缓存级别:SESSION/STATEMENT-->
        <setting name="localCacheScope" value="STATEMENT"/>
    </settings>

    ...
</configuration>
  • 缓存的执行策略为二级缓存、一级缓存和数据库,所以这类的缓存配置可以根据代码测试阶段调整为一级、二级,交叉开启和关闭进行验证。

4.1.2 缓存策略配置

Activity_Mapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lino.mybatis.test.dao.IActivityDao">

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

    <resultMap id="activityMap" type="com.lino.mybatis.test.po.Activity">
        <id column="id" property="id"/>
        <result column="activity_id" property="activityId"/>
        <result column="activity_name" property="activityName"/>
        <result column="activity_desc" property="activityDesc"/>
        <result column="create_time" property="createTime"/>
        <result column="update_time" property="updateTime"/>
    </resultMap>

    <select id="queryActivityById" parameterType="com.lino.mybatis.test.po.Activity" resultMap="activityMap">
        SELECT activity_id, activity_name, activity_desc, create_time, update_time
        FROM activity
        <trim prefix="where" prefixOverrides="AND | OR" suffixOverrides="and">
            <if test="null != activityId">
                activity_id = #{activityId}
            </if>
        </trim>
    </select>

</mapper>
  • cache 标签为二级缓存的使用策略,你可以配置 FIFO、LRU 等不同的缓存策略。

4.2 单元测试

ApiTest.java

@Test
public void test_queryActivityById() throws IOException {
    // 1.从SqlSessionFactory中获取SqlSession
    Reader reader = Resources.getResourceAsReader("mybatis-config-datasource.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

    // 2.请求对象
    Activity activity = new Activity();
    activity.setActivityId(100001L);

    // 3.第一组:SqlSession
    // 3.1 开启session
    SqlSession sqlSession01 = sqlSessionFactory.openSession();
    // 3.2 获取映射器对象
    IActivityDao dao01 = sqlSession01.getMapper(IActivityDao.class);
    logger.info("测试结果01:{}", JSON.toJSONString(dao01.queryActivityById(activity)));
    sqlSession01.close();

    // 4.第二组:SqlSession
    // 4.1 开启session
    SqlSession sqlSession02 = sqlSessionFactory.openSession();
    // 4.2 获取映射器对象
    IActivityDao dao02 = sqlSession02.getMapper(IActivityDao.class);
    logger.info("测试结果02:{}", JSON.toJSONString(dao02.queryActivityById(activity)));
    sqlSession02.close();
}
  • 在单元测试中,分别两次开启 openSession 操作,并在第一次开启会话查询数据后执行会话关闭。
    • 因为实际代码实现,无论是 commit、close 结束会话,都会把一级缓存数据刷新到二级缓存,所以两个方式都可以。
  • 当会话关闭后开始执行第二次的会话开启,验证数据是否从二级缓存中获取数据,因为在二级缓存中添加了日志判断,所以可以通过打印日志进行验证。

测试结果

10:43:34.690 [main] INFO  c.l.m.d.pooled.PooledDataSource - Created connention 899644639.
拦截SQLSELECT activity_id, activity_name, activity_desc, create_time, update_time
        FROM activity
         where activity_id = ?
10:43:34.700 [main] INFO  c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:100001
10:43:34.700 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果01{"activityDesc":"测试活动","activityId":100001,"activityName":"活动名","createTime":1628424890000,"updateTime":1628424890000}
10:43:34.700 [main] INFO  c.l.m.d.pooled.PooledDataSource - Returned connection 899644639 to pool.
10:43:34.700 [main] INFO  c.l.m.s.defaults.DefaultSqlSession - 执行查询 statement:com.lino.mybatis.test.dao.IActivityDao.queryActivityById parameter:{"activityId":100001}
10:43:34.700 [main] INFO  c.l.mybatis.builder.SqlSourceBuilder - 构建参数映射 property:activityId propertyType:class java.lang.Long
10:43:34.700 [main] DEBUG c.lino.mybatis.executor.BaseExecutor - 二级缓存: [{"activityDesc":"测试活动","activityId":100001,"activityName":"活动名","createTime":1628424890000,"updateTime":1628424890000}]
10:43:34.700 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果02{"activityDesc":"测试活动","activityId":100001,"activityName":"活动名","createTime":1628424890000,"updateTime":1628424890000}

在这里插入图片描述

  • 从断点调试和打印的日志看,此时框架已经可以在两次会话中完成数据的二次缓存使用。

五、总结:二级缓存

  • 可以从二级缓存的设计实现上,看到 Mybatis 框架在这里运用了大量的装饰器模式,如 CachingExecutor 执行器和 Cache 接口的各类实现。
    • 这样的设计的好处可以在不破坏原有逻辑的前提下,完成功能通过配置开关的自由开启使用。
  • 虽然二级缓存在 Mybatis 框架中也是一个不错的设计,但由于系统架构逐步从单体到分布式以后,单个实例的数据缓存并没有太大的意义。
    • 因为在分布式架构下,用户的每次请求会分散到不同应用实例上,那么单个缓存这个时候大概率就没法起到作用了。
    • 所以在使用 Mybatis 时通常并不会开启二级缓存,而是使用 Redis 这样的框架来预热数据库数据使用。

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

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

相关文章

STM32CUBEMX_创建时间片轮询架构的软件框架

STM32CUBEMX_创建时间片轮询架构的软件框架 说明&#xff1a; 1、这种架构避免在更新STM32CUBEMX配置后把用户代码清除掉 2、利用这种时间片的架构可以使得代码架构清晰易于维护 创建步骤&#xff1a; 1、使用STM32CUBEMX创建基础工程 2、新建用户代码目录 3、构建基础的代码框…

uniapp制作——交友盲盒

在小程序端可以有很多好玩的小玩意&#xff0c;目前网上比较有趣的就是有一个交友盲盒&#xff0c;能抽出和找出对象的一个有趣的小程序&#xff0c;所以今天给大家带来用uniapp搭建的交友盲盒&#xff0c;大家再根据自己的情况去搭建自己的后端和数据库来完成自己的一个小项目…

实现Android APK瘦身99.99%

摘要&#xff1a; 如何瘦身是 APK 的重要优化技术。APK 在安装和更新时都需要经过网络下载到设备&#xff0c;APK 越小&#xff0c;用户体验越好。本文作者通过对 APK 内在机制的详细解析&#xff0c;给出了对 APK 各组成成分的优化方法及技术&#xff0c;并实现了一个基本 APK…

制药行业GMP是什么?

药品制造是一项极其关键的行业&#xff0c;它直接涉及到人民的健康和生命安全。因此&#xff0c;确保药品质量、安全性和有效性至关重要。为了实现这一目标&#xff0c;全球范围内都实施了药品生产质量管理规范&#xff0c;通常被简称为GMP&#xff08;Good Manufacturing Prac…

Java8新特性stream和parallelStream有什么区别

1 stream和parallelStream的区别 1.Stream 是在 Java8 新增的特性&#xff0c;普遍称其为流&#xff1b;它不是数据结构也不存放任何数据&#xff0c;其主要用于集合的逻辑处理。 2.Stream流是一个集合元素的函数模型&#xff0c;它并不是集合&#xff0c;也不是数据结构&…

是真卷?还是内耗?这次面试造的火箭着实有点离谱

大家好&#xff0c;我是冰河~~ 都说面试造火箭&#xff0c;工作拧螺丝&#xff0c;不过这次面试造的着实有点离谱&#xff01; 事情是这样的&#xff0c;昨天在冰河的知识星球微信群里&#xff0c;一名小伙伴发了一张面试常州一家公司的面试题的图片&#xff0c;瞬间吸引了我…

十个响应式页面项目

十个响应式页面项目 上接 53 个特效&#xff1a; 53 个 CSS 特效 153 个 CSS 特效 253 个 CSS 特效 3&#xff08;完&#xff09; 照例&#xff0c;预览地址在 http://www.goldenaarcher.com/html-css-js-proj/&#xff0c;git 地址&#xff1a; https://github.com/Goldena…

Seata处理分布式事务之1.7.0

https://blog.csdn.net/zhang33565417/article/details/122768300 1.5.0之后版本发生了很大改变 1.seata安装 1.1官网地址 http://seata.io/zh-cn/ 1.2下载地址 https://github.com/seata/seata/releases 下载的是seata-server-1.7.0.zip 1.3seata相关配置的修改 seata-…

Python学习 -- Math模块和Random模块

math 模块提供了许多数学函数&#xff0c;用于执行各种数学运算。以下是一些常用的 math 函数以及相应的示例代码&#xff1a; math.sqrt(x): 计算平方根。 import math x 25 square_root math.sqrt(x) print(f"√{x} {square_root}") math.pow(x, y): 计算 x …

好用的电容笔有哪些推荐?适合开学买电容笔推荐

尤其是在苹果品牌推出了专属Pencil系列之后&#xff0c;苹果原装的Pencil系列产品&#xff0c;更是将价格压到了所有人都无法企及的地步。市场上有不少可以替代苹果Pencil的平替电容笔&#xff0c;无论是做笔记还是专业的绘画&#xff0c;都完全足够了。在此&#xff0c;我将为…

电视盒子什么品牌好?数码博主盘点目前性能最好的电视盒子

电视盒子是非常重要的&#xff0c;老人小孩基本每天都会看电视&#xff0c;而电视盒子作为电视盒子的最佳拍档销量十分火爆&#xff0c;我自己每个月都会测评几次电视盒子&#xff0c;今天给大家详细解读一下电视盒子什么品牌好&#xff0c;看看目前性能最好的电视盒子是哪些&a…

电力智能监控系统

电力智能监控系统依托电易云-智慧电力物联网&#xff0c;利用计算机、计量保护装置和总线技术&#xff0c;对中、低压配电系统的实时数据、开关状态及远程控制进行了集中管理。该电力监控系统可以为企业提供"监控一体化"的整体解决方案&#xff0c;主要包括实时历史数…

AutoHotkey(AHK)脚本,自动截图当前屏幕并发送给微信窗口

前提先安装好 AutoHotkey &#xff0c;本脚本依赖AutoHotkey 环境 首先 &#xff0c;设置微信的快捷键 执行代码如下&#xff1a; Loop {SendInput, {Alt down}s{Alt up}Sleep, 2000 ; 等待2秒; 双击鼠标左键Click, 2Sleep, 1000 ; 等待1秒SendInput, {Alt down}a{Alt up}Sl…

systemverilog运行的时候调用系统函数运行python等

systemverilog 运行的时候使用系统函数 使用场景&#xff0c;在仿真过程中&#xff0c;需要外部环境准备仿真参数&#xff0c;或者调整仿真参数的时候 创建一个python文件 print("123")创建一个sv文件 module dut ;initial begin$system("python 123.py"…

【补充】助力工业物联网,工业大数据之AirFlow安装

【补充】助力工业物联网&#xff0c;工业大数据之AirFlow安装 直接在node1上安装 1、安装Python 安装依赖 yum -y install zlib zlib-devel bzip2 bzip2-devel ncurses ncurses-devel readline readline-devel openssl openssl-devel openssl-static xz lzma xz-devel sqlit…

WebStorm软件安装包分享(附安装教程)

目录 一、软件简介 二、软件下载 一、软件简介 WebStorm是一款由JetBrains开发的集成开发环境&#xff08;IDE&#xff09;&#xff0c;专门用于开发Web应用程序。它支持多种前端技术&#xff0c;如HTML、CSS、JavaScript、TypeScript等&#xff0c;并提供了丰富的功能和工具&…

ICL7106芯片的特性、应用与重要性 | 百能云芯

ICL7106 是一种专用集成电路 (IC)&#xff0c;在模数转换中发挥着至关重要的作用。它是一种通用且广泛使用的 ADC&#xff0c;可在各种电子应用中提供高精度测量。下面小芯为您详细解析ICL7106 是什么、它的特性、应用及其在电子领域的重要性。 ICL7106 是 Intersil&#xff08…

记一次Nginx代理Mysql服务的经历

背景&#xff1a; 根据组长背景描述&#xff0c;具备以下前提 1. Mysql服务器为 某A云厂商的RDS SAAS服务&#xff0c;但是不开通外网服务 2. EC2 服务器一台&#xff0c;某A云厂商LaaS服务&#xff0c;也不开通外网 3.阿里云服务器一台&#xff0c;这台服务器有服务需要连…

巨人互动|游戏出海游戏出海效果怎样?

游戏出海是指将原本面向国内市场的游戏产品进行调整和优化&#xff0c;以适应海外市场的需求&#xff0c;并进行推广和销售。下面小编讲讲关于游戏出海对于游戏效果的影响的一些讨论点。 1、市场扩大 通过游戏出海&#xff0c;可以将游戏产品的目标受众从国内扩展到全球范围内…

博物学欣赏

自文艺复兴以降&#xff0c;西方开启发现世界的旅程。 这些东西对于科学、地理学、考古学、探险、旅游学、博物学、绘画学、美学无疑有着至高无上的借鉴价值。我们今天出版这些图文并茂的书籍有如斯高远的志向和目标&#xff1a; 展现自然的历史风貌 呈现万物的生态原样 复现…