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

文章目录

  • 一、目标:二级缓存
  • 二、设计:二级缓存
  • 三、实现:二级缓存
    • 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 的操作时使用。

手写Mybatis:第19章-二级缓存_第1张图片

  • 首先要在 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 二级缓存类图

手写Mybatis:第19章-二级缓存_第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            返回的类型
     * @return List
     * @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            返回的类型
     * @return List
     * @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.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 * FROM PERSON WHERE ID = #{id}
     * 
     */
    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);

        // 解析
        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);
    }

    ...

    /**
     * 
     * SELECT LAST_INSERT_ID()
     * 
     */
    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元素
     * 
     * 
     * 
     *
     * @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"));
    }

    /**
     * 
     */
    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"));

        // 解析额外属性信息:
        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>
    
    <setting name="cacheEnabled" value="true"/>
    
    <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 {

    ...

    /**
     * 
     * 
     * 
     * 
     */
    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


DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    ...

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

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

4.1.2 缓存策略配置

Activity_Mapper.xml


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:第19章-二级缓存_第3张图片

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

五、总结:二级缓存

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

你可能感兴趣的:(手写mybatis,mybatis,spring,java)