MyBatis缓存

MyBatis是常见的Java数据库访问层框架. 在日常工作中, 开发人员多数情况下是使用MyBatis的默认缓存配置, 但是MyBatis缓存机制有一些不足之处, 在使用中容易引起脏数据, 形成一些潜在的隐患. 个人在业务开发中也处理过一些由于MyBatis缓存引发的开发问题, 带着个人的兴趣, 希望从应用及源码的角度为读者梳理MyBatis缓存机制.

1. 一级缓存

1.1. 一级缓存介绍

MyBatis一级缓存, 又名session级别缓存, 默认开启. 在应用运行过程中, 可能在一次数据库会话中, 执行多次相同的SQL. MyBatis为了优化这种情况提供了一级缓存, 如果是相同的SQL语句, 会优先命中一级缓存, 避免直接对数据库进行查询.

一级缓存执行过程

在参数和sql完全一致的情况下, 使用同一个SqlSession对象多次调用同一个mapper的方法查询数据时, 往往只执行一次sql. 因为通过sqlSession创建的Executor(SimpleExecutor)中, 其内部维护了一个localCache(PrepetualCache), 该类继承自Cache类, 内部维护一个HashMap, 用以保存查询结果. 在第一次查询后, 将数据存入localCache中, 再次查询时, 从中获取已经缓存的数据, 而不会发送sql进行查询. 当openSession的方法运行完毕或者主动调用了SqlSession的close方法, 一级缓存也将会被清空.

一级缓存类关系图

1.2. 一级缓存配置

对于一级缓存默认是开启session级别的, 可以在MyBatis配置文件中添加如下代码进行修改.

<setting name="localCacheScope" value="SESSION"/>

value可选为session和statement.

  • session可以理解为在一次会话中执行的所有sql语句, 都会共享这个缓存.

  • statement可以理解为缓存只对当前执行的这个statement有效.

1.3. 一级缓存工作流程

一级缓存工作流程

MyBatis查询相关核心类介绍
  • SqlSession: 对外提供数据库操作的所有方法, 默认实现类是DefaultSqlSession.

  • Executor: SqlSession向用户提供数据库操作的方法, 但和数据库操作有关的职责都会委托给Executor. Executor有若干个实现类, 为Executor赋予了不同的能力. 此处主要查看抽象类BaseExecutor.

  • Cache: MyBatis中的缓存接口, 提供了和缓存相关的基本操作. Cache有若干个实现类, 使用装饰器模式相互组装.

public abstract class BaseExecutor implements Executor {
    protected PerpetualCache localCache;
}
public class PerpetualCache implements Cache {
    private Map<Object, Object> cache = new HashMap<>();
}

在BaseExecutor中持有了一个Cache接口的最基本实现: PerpetualCache. 其实现比较简单, 操作缓存其实就是操作内部持有的HashMap.

public class DefaultSqlSessionFactory implements SqlSessionFactory {
    @Override
    public SqlSession openSession() {
        return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
    }

    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;
        try {
            final Environment environment = configuration.getEnvironment();
            final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            final Executor executor = configuration.newExecutor(tx, execType); // 创建executor
            return new DefaultSqlSession(configuration, executor, autoCommit);
        } catch (Exception e) {
            closeTransaction(tx); // may have fetched a connection so lets call close()
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
        } finally {
            ErrorContext.instance().reset();
        }
    }
}

public class Configuration {

    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        executorType = executorType == null ? defaultExecutorType : executorType;
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
        Executor executor;
        if (ExecutorType.BATCH == executorType) {
            executor = new BatchExecutor(this, transaction);
        } else if (ExecutorType.REUSE == executorType) {
            executor = new ReuseExecutor(this, transaction);
        } else {
            executor = new SimpleExecutor(this, transaction);
        }
        if (cacheEnabled) {
            // 注意这里, 如果开启二级缓存, 是使用CachingExecutor装饰BaseExecutor的子类
            executor = new CachingExecutor(executor);
        }
        executor = (Executor) interceptorChain.pluginAll(executor);
        return executor;
    }

}

为执行和数据库的交互, 通过DefaultSessionFactory初始化SqlSession. 在初始化SqlSession时, 会使用Configuration创建一个全新的Executor, 作为DefaultSqlSession构造函数的参数.

public class DefaultSqlSession implements SqlSession {
    @Override
    public <E> List<E> selectList(String statement, Object parameter) {
        return this.selectList(statement, parameter, RowBounds.DEFAULT);
    }
    
    @Override
    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        try {
            MappedStatement ms = configuration.getMappedStatement(statement);
            return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
        } finally {
            ErrorContext.instance().reset();
        }
    }
}
public abstract class BaseExecutor implements Executor {
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameter);
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
    
    @Override
    public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }
        CacheKey cacheKey = new CacheKey();
        cacheKey.update(ms.getId());
        cacheKey.update(rowBounds.getOffset());
        cacheKey.update(rowBounds.getLimit());
        cacheKey.update(boundSql.getSql());
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
        // mimic DefaultParameterHandler logic
        for (ParameterMapping parameterMapping : parameterMappings) {
            if (parameterMapping.getMode() != ParameterMode.OUT) {
                Object value;
                String propertyName = parameterMapping.getProperty();
                if (boundSql.hasAdditionalParameter(propertyName)) {
                    value = boundSql.getAdditionalParameter(propertyName);
                } else if (parameterObject == null) {
                    value = null;
                } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                    value = parameterObject;
                } else {
                    MetaObject metaObject = configuration.newMetaObject(parameterObject);
                    value = metaObject.getValue(propertyName);
                }
                cacheKey.update(value);
            }
        }
        if (configuration.getEnvironment() != null) {
            // issue #176
            cacheKey.update(configuration.getEnvironment().getId());
        }
        return cacheKey;
    }
}

SqlSession创建完毕后, 根据Statment的不同类型, 会进入SqlSession的不同方法中. 如果是Select语句的话, 最后会执行到SqlSession的selectList. 在selectList方法中, SqlSession把具体的查询职责委托给了Executor. 如果只开启了一级缓存的话, 首先会进入BaseExecutor的query方法.

在BaseExecutor的createCacheKey中, 根据MappedStatement的Id、SQL的offset、SQL的limit、SQL本身以及SQL中的参数传入了CacheKey这个类, 最终构成CacheKey.

public class CacheKey implements Cloneable, Serializable {

    private static final int DEFAULT_MULTIPLYER = 37;
    private static final int DEFAULT_HASHCODE = 17;

    private final int multiplier;
    private int hashcode;
    private long checksum;
    private int count;
    private List<Object> updateList;
    
    public CacheKey() {
        this.hashcode = DEFAULT_HASHCODE;
        this.multiplier = DEFAULT_MULTIPLYER;
        this.count = 0;
        this.updateList = new ArrayList<>();
    }
    
    public void update(Object object) {
        int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
        count++;
        checksum += baseHashCode;
        baseHashCode *= count;
        hashcode = multiplier * hashcode + baseHashCode;
        updateList.add(object);
    }
    
    @Override
    public boolean equals(Object object) {
        if (this == object) {
            return true;
        }
        if (!(object instanceof CacheKey)) {
            return false;
        }

        final CacheKey cacheKey = (CacheKey) object;

        if (hashcode != cacheKey.hashcode) {
            return false;
        }
        if (checksum != cacheKey.checksum) {
            return false;
        }
        if (count != cacheKey.count) {
            return false;
        }

        for (int i = 0; i < updateList.size(); i++) {
            Object thisObject = updateList.get(i);
            Object thatObject = cacheKey.updateList.get(i);
            if (!ArrayUtil.equals(thisObject, thatObject)) {
                return false;
            }
        }
        return true;
    }
}

首先是成员变量和构造函数, 有一个初始的hachcode和乘数, 同时维护了一个内部的updatelist. 在CacheKey的update方法中, 会进行一个hashcode和checksum的计算, 同时把传入的参数添加进updatelist中.

根据CacheKey的equals可以知道, 除了hashcode、checksum、count相等之外, 还需要updatelist中的元素一一对应相等, 才能认为两个CacheKey相等. 从SQL来看, 只要Statement Id + Offset + Limmit + Sql + Params相同, 才能判定两条SQL相同.

public abstract class BaseExecutor implements Executor {
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }
        if (queryStack == 0 && ms.isFlushCacheRequired()) {
            clearLocalCache();
        }
        List<E> list;
        try {
            queryStack++;
            list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
            if (list != null) {
                // 这个主要是处理存储过程用的. 
                handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
            } else {
                list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
            }
        } finally {
            queryStack--;
        }
        if (queryStack == 0) {
            for (DeferredLoad deferredLoad : deferredLoads) {
                deferredLoad.load();
            }
            // issue #601
            deferredLoads.clear();
            if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                // issue #482 STATEMENT级别时, 清空缓存.
                clearLocalCache();
            }
        }
        return list;
    }

    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        List<E> list;
        localCache.putObject(key, EXECUTION_PLACEHOLDER);
        try {
            list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
            localCache.removeObject(key);
        }
        localCache.putObject(key, list); // 将查询的结果放入缓存中
        if (ms.getStatementType() == StatementType.CALLABLE) {
            localOutputParameterCache.putObject(key, parameter);
        }
        return list;
    }
}

继续查看BaseExecutor的query方法, 在做实际查询之前, 先从localCache中获取, 如果获取不到, 就调用queryFromDatabase方法从数据库中查询. 在queryFromDatabase方法中, 会将查询的结果放入缓存中. 在query方法的最后, 判断缓存是否是STATEMENT级别, 如果是, 就清空缓存, 这也是STATEMENT级别的一级缓存无法共享localCache的原因.

最后, 简单分析下insert/delete/update方法. 了解下缓存会刷新的原因.

public class DefaultSqlSession implements SqlSession {
    @Override
    public int delete(String statement) {
        return update(statement, null);
    }
    @Override
    public int delete(String statement, Object parameter) {
        return update(statement, parameter);
    }

    @Override
    public int insert(String statement) {
        return insert(statement, null);
    }

    @Override
    public int insert(String statement, Object parameter) {
        return update(statement, parameter);
    }

    @Override
    public int update(String statement) {
        return update(statement, null);
    }

    @Override
    public int update(String statement, Object parameter) {
        try {
            dirty = true;
            MappedStatement ms = configuration.getMappedStatement(statement);
            return executor.update(ms, wrapCollection(parameter));
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
        } finally {
            ErrorContext.instance().reset();
        }
    }
}
public abstract class BaseExecutor implements Executor {

    @Override
    public int update(MappedStatement ms, Object parameter) throws SQLException {
        ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }
        clearLocalCache(); // 清空缓存
        return doUpdate(ms, parameter);
    }
    @Override
    public void clearLocalCache() {
        if (!closed) {
            localCache.clear();
            localOutputParameterCache.clear();
        }
    }
}

SqlSession的insert和delete方法, 最后都会调用update方法. 与query方法类似的, update方法也是委托给Executor执行. 可以看到, 每次在执行update方法前都会清空localCache.

2. 二级缓存

2.1 二级缓存介绍

在一级缓存中, 其最大的共享范围就是SqlSession内部, 如果多个SqlSession之间需要共享缓存, 则需要使用到二级缓存. 开启二级缓存后, 会使用CachingExecutor装饰Executor, 进入一级缓存的查询流程前, 先在CachingExecutor进行二级缓存的查询, 并且同一个namespace下的所有操作语句, 都影响着同一个Cache, 即二级缓存被多个SqlSession共享, 是一个全局的变量.

此时, 数据库的查询流程就是: 二级缓存 -> 一级缓存 -> 数据库

二级缓存

2.2. 二级缓存配置

使用二级缓存, 首先需要在MyBatis配置文件下进行配置.

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

之后, 需要在MyBatis的mapper文件中配置cache或者cache-ref.

<cache />
<cache-ref namespace="xx.AbcMapper" />

cache标签用于声明这个namespace使用二级缓存, 并且可以自定义配置.

  • type: cache使用的类型, 默认是PerpetualCache, 这在一级缓存中提到过.
  • eviction: 定义回收的策略, 常见的有FIFO, LRU.
  • flushInterval: 配置一定时间自动刷新缓存, 单位是毫秒.
  • size: 最多缓存对象的个数.
  • readOnly: 是否只读, 若配置可读写, 则需要对应的实体类能够序列化.
  • blocking: 若缓存中找不到对应的key, 是否会一直blocking, 直到有对应的数据进入缓存.

cache-ref代表引用其他命名空间的cache配置, 两个命名空间操作同一个cache.

2.3 二级缓存工作流程

MyBatis二级缓存的工作流程和一级缓存类似,只是在一级缓存处理前,用CachingExecutor装饰了BaseExecutor的子类,在委托具体职责给delegate之前,实现了二级缓存的查询和写入功能,具体类关系图如下图所示。

二级缓存类关系

public class Configuration {

    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        executorType = executorType == null ? defaultExecutorType : executorType;
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
        Executor executor;
        if (ExecutorType.BATCH == executorType) {
            executor = new BatchExecutor(this, transaction);
        } else if (ExecutorType.REUSE == executorType) {
            executor = new ReuseExecutor(this, transaction);
        } else {
            executor = new SimpleExecutor(this, transaction);
        }
        if (cacheEnabled) {
            // 注意这里, 如果开启二级缓存, 是使用CachingExecutor装饰BaseExecutor的子类
            executor = new CachingExecutor(executor);
        }
        executor = (Executor) interceptorChain.pluginAll(executor);
        return executor;
    }
}

在创建Executor的方法中, 可以看到最后返回的CachingExecutor. 查看CachingExecutor的query方法.

public class CachingExecutor implements Executor {
    private final Executor delegate;
    private final TransactionalCacheManager tcm = new TransactionalCacheManager();

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        Cache cache = ms.getCache(); // 获取Cache
        if (cache != null) {
            flushCacheIfRequired(ms); // 判断是否需要刷新缓存
            if (ms.isUseCache() && resultHandler == null) {
                ensureNoOutParams(ms, boundSql); // 主要是用来处理存储过程
                @SuppressWarnings("unchecked")
                List<E> list = (List<E>) tcm.getObject(cache, key);
                if (list == null) {
                    list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                    tcm.putObject(cache, key, list); // issue #578 and #116
                }
                return list;
            }
        }
        return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }
    
    private void flushCacheIfRequired(MappedStatement ms) {
        Cache cache = ms.getCache();
        // 如果不是select语句的话, 此处会先清除tcm中的暂存缓存, 并设置clearOnCommit为true
        if (cache != null && ms.isFlushCacheRequired()) {
            tcm.clear(cache);
        }
    }
}

在CachingExecutor的query方法中,首先会从MappedStatement中获得在配置初始化时赋予的Cache。本质上是装饰器的使用, 不同的组合, 为Cache赋予了不同的能力, 具体的装饰链是: SynchronizedCache -> LoggingCache -> SerializedCache -> LruCache -> PerpetualCache。

Cache装饰器

  • SynchronizedCache:同步Cache,实现比较简单,直接使用synchronized修饰方法。
  • LoggingCache:日志功能,装饰类,用于记录缓存的命中率,如果开启了DEBUG模式,则会输出命中率日志。
  • SerializedCache:序列化功能,将值序列化后存到缓存中。该功能用于缓存返回一份实例的Copy,用于保存线程安全。
  • LruCache:采用了Lru算法的Cache实现,移除最近最少使用的Key/Value。
  • PerpetualCache: 作为为最基础的缓存类,底层实现比较简单,直接使用了HashMap。

然后是判断是否需要刷新缓存flushCacheIfRequired(ms), 在默认的设置中SELECT语句不会刷新缓存,insert/update/delte会刷新缓存。

在继续阅读源码之前, 先了解下TransactionalCache与TransactionalCacheManager.

**TransactionalCache: **继承自Cache接口,主要作用是保存SqlSession在事务中需要向某个二级缓存提交的缓存数据(因为事务过程中的数据可能会回滚,所以不能直接把数据就提交二级缓存,而是暂存在TransactionalCache中,在事务提交后再将存放其中的数据提交到二级缓存,如果事务回滚,则将数据清除掉)

public class TransactionalCache implements Cache {

    private final Cache delegate; // 对应的二级缓存
    private boolean clearOnCommit; // 是否在commit之后, 清除二级缓存
    private final Map<Object, Object> entriesToAddOnCommit; // 暂存的数据, 事务提交后放入二级缓存中.
    private final Set<Object> entriesMissedInCache; // 缓存未命中的数据, 事务提交后放入二级缓存中.

    public TransactionalCache(Cache delegate) {
        this.delegate = delegate;
        this.clearOnCommit = false;
        this.entriesToAddOnCommit = new HashMap<>();
        this.entriesMissedInCache = new HashSet<>();
    }

    @Override
    public Object getObject(Object key) {
        // issue #116
        Object object = delegate.getObject(key); // 二级缓存中查询
        if (object == null) {
            entriesMissedInCache.add(key); 
        }
        // issue #146
        if (clearOnCommit) {
            return null;
        } else {
            return object;
        }
    }
    @Override
    public void clear() {
        clearOnCommit = true;
        entriesToAddOnCommit.clear(); // 需要注意的是此处清除的是暂存数据
    }
    @Override
    public void putObject(Object key, Object object) {
        entriesToAddOnCommit.put(key, object); // 将数据暂存
    }

    public void commit() {
        if (clearOnCommit) {
            // 在执行update/insert/delete的SQL语句时, 在flushCacheIfRequired方法中会将clearOnCommit设定为true, 
            // 此时在执行该方法时, 清除二级缓存.
            delegate.clear(); 
        }
        flushPendingEntries(); // 暂存数据放入二级缓存
        reset();
    }

    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);
            }
        }
    }
}

TransactionalCacheManager:用于管理CachingExecutor使用的二级缓存对象,其实现比较简单, 其内部只定义了一个Map, 保存了Cache和TransactionalCache包装后的Cache的映射关系, Key是CachingExecutor使用的二级缓存, value是TransactionalCache对象, 所有的方法都是调用TransactionalCache中对应的方法.

public class TransactionalCacheManager {
    private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
    
    private TransactionalCache getTransactionalCache(Cache cache) {
        return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
    }
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    Cache cache = ms.getCache(); // 获取Cache
    if (cache != null) {
        flushCacheIfRequired(ms); // 判断是否需要刷新缓存
        if (ms.isUseCache() && resultHandler == null) {
            ensureNoOutParams(ms, boundSql); // 主要是用来处理存储过程
            @SuppressWarnings("unchecked")
            List<E> list = (List<E>) tcm.getObject(cache, key);
            if (list == null) {
                list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                tcm.putObject(cache, key, list); // issue #578 and #116
            }
            return list;
        }
    }
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

之后回到CachingExecutor的query方法中. tcm.getObject(cache, key)方法尝试从二级缓存中查找数据. 在getObject方法中会将获取值的职责一路传递, 最终调用PerpetualCache的getObject方法. 如果未查询到数据, 会把Key放入Miss集合中, 用于统计命中率. 之后回到query方法中, 调用delegate的query方法, 此处调用的其实就是BaseExecutor的query方法. 最后, 调用tcm.putObject(cache, key, list), 注意, 此处只是将结果放入待提交的Map中, 并没有直接操作二级缓存. 只要不调用commit方法的话, 就不会对二级缓存造成影响.

public class DefaultSqlSession implements SqlSession {
    @Override
    public void commit() {
        commit(false);
    }

    @Override
    public void commit(boolean force) {
        try {
            executor.commit(isCommitOrRollbackRequired(force));
            dirty = false;
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + e, e);
        } finally {
            ErrorContext.instance().reset();
        }
    }
}

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

public class TransactionalCacheManager {
    public void commit() {
        for (TransactionalCache txCache : transactionalCaches.values()) {
            txCache.commit();
        }
    }
}

public class TransactionalCache implements Cache {
    public void commit() {
        if (clearOnCommit) {
            delegate.clear(); // 清除二级缓存, 如果需要的话.
        }
        flushPendingEntries(); // 暂存数据放入二级缓存
        reset();
    }

    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);
            }
        }
    }
}

当调用SqlSession的commit方法后, 会先进入CachingExecutor实现的commit方法. 在CachingExecutor 的commit中, 将具体的commit职责委托给delegate, 之后调用TransactionalCacheManager的commit方法刷新缓存, 最终又会到TransactionalCache的commit方法.

最后在看下insert/delete/update方法, 其最后都会进入update方法

public class CachingExecutor implements Executor {
    @Override
    public int update(MappedStatement ms, Object parameterObject) throws SQLException {
        flushCacheIfRequired(ms);
        return delegate.update(ms, parameterObject);
    }
}

在flushCacheIfRequired方法中, 会将TransactionalCache的clearOnCommit设为true, 最后在commit时, 会将二级缓存清空.

3. 总结

Mybatis的一级缓存内部设计比较简单, 只是一个无限容量的HashMap, 在缓存功能上有所欠缺. 其最大范围是SqlSession内部, 如果存在多个SqlSession或者分布式环境下, 数据库写操作会引起脏数据, 所以建议设定缓存级别为Statement.

MyBatis的二级缓存相对一级缓存来说, 实现了SqlSession之间的数据共享, 通过Cache接口实现类的不同组合, 对Cache的可控性也更强. 但是也有很大的局限性, 比如在多表查询(使用不同的namespace情况下), 极大可能出现脏数据. 而且对于细粒度的数据缓存实现的不太好(比如update之后, 要求只刷新update的数据而不是全盘刷新), 并且默认缓存实现基于本地, 也无法在分布式环境下使用.

posted on 2021-02-10 15:15  annwyn  阅读(142)  评论(0)    收藏  举报

导航