登录
首页 >  数据库 >  MySQL

Mybatis源码-缓存机制

来源:SegmentFault

时间:2023-01-22 17:00:52 397浏览 收藏

积累知识,胜过积蓄金银!毕竟在##column_title##开发的过程中,会遇到各种各样的问题,往往都是一些细节知识点还没有掌握好而导致的,因此基础知识点的积累是很重要的。下面本文《Mybatis源码-缓存机制》,就带大家讲解一下MySQL、缓存、jdbc、mybatis、缓存设计知识点,若是你对本文感兴趣,或者是想搞懂其中某个知识点,就请你继续往下看吧~

正文

一. 一级缓存机制展示

其中localCacheScope可以配置为SESSION(默认)或者STATEMENT,含义如下所示。

属性值含义
SESSION一级缓存在一个会话中生效。即在一个会话中的所有查询语句,均会共享同一份一级缓存,不同会话中的一级缓存不共享。
STATEMENT一级缓存仅针对当前执行的

映射接口如下所示。

public interface BookMapper {

    Book selectBookById(int id);

}

映射文件如下所示。

public class MybatisTest {

    public static void main(String[] args) throws Exception {
        String resource = "mybatis-config.xml";
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
                .build(Resources.getResourceAsStream(resource));
        SqlSession sqlSession = sqlSessionFactory.openSession(false);
        BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);

        System.out.println(bookMapper.selectBookById(1));
        System.out.println(bookMapper.selectBookById(1));
        System.out.println(bookMapper.selectBookById(1));
    }

}

在执行代码中,连续执行了三次查询操作,看一下日志打印,如下所示。

可以知道,只有第一次查询时和数据库进行了交互,后面两次查询均是从一级缓存中查询的数据。现在往映射接口和映射文件中加入更改数据的逻辑,如下所示。

public interface BookMapper {

    Book selectBookById(int id);
    // 根据id更改图书价格
    void updateBookPriceById(@Param("id") int id, @Param("bookPrice") float bookPrice);

}


        UPDATE
        book
        SET
        b_price=#{bookPrice}
        WHERE
        id=#{id}
    

执行的操作为先执行一次查询操作,然后执行一次更新操作并提交事务,最后再执行一次查询操作,执行代码如下所示。

public class MybatisTest {

    public static void main(String[] args) throws Exception {
        String resource = "mybatis-config.xml";
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
                .build(Resources.getResourceAsStream(resource));
        SqlSession sqlSession = sqlSessionFactory.openSession(false);
        BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);

        System.out.println(bookMapper.selectBookById(1));

        System.out.println("Change database.");
        bookMapper.updateBookPriceById(1, 22.5f);
        sqlSession.commit();

        System.out.println(bookMapper.selectBookById(1));
    }

}

执行结果如下所示。

通过上述结果可以知道,在执行更新操作之后,再执行查询操作时,是直接从数据库查询的数据,并未使用一级缓存,即在一个会话中,对数据库的操作,均会使一级缓存失效。

现在在执行代码中创建两个会话,先让会话1执行一次查询操作,然后让会话2执行一次更新操作并提交事务,最后让会话1再执行一次相同的查询。执行代码如下所示。

public class MybatisTest {

    public static void main(String[] args) throws Exception {
        String resource = "mybatis-config.xml";
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
                .build(Resources.getResourceAsStream(resource));
        SqlSession sqlSession1 = sqlSessionFactory.openSession(false);
        SqlSession sqlSession2 = sqlSessionFactory.openSession(false);
        BookMapper bookMapper1 = sqlSession1.getMapper(BookMapper.class);
        BookMapper bookMapper2 = sqlSession2.getMapper(BookMapper.class);

        System.out.println(bookMapper1.selectBookById(1));

        System.out.println("Change database.");
        bookMapper2.updateBookPriceById(1, 22.5f);
        sqlSession2.commit();

        System.out.println(bookMapper1.selectBookById(1));
    }

}

执行结果如下所示。

上述结果表明,会话1的第一次查询是直接查询的数据库,然后会话2执行了一次更新操作并提交了事务,此时数据库中id为1的图书的价格已经变更为了22.5,紧接着会话1又做了一次查询,但查询结果中的图书价格为20.5,说明会话1的第二次查询是从缓存获取的查询结果。所以在这里可以知道,

@Override
public  List query(MappedStatement ms, Object parameter, RowBounds rowBounds, 
                       ResultHandler resultHandler) throws SQLException {
    // 获取Sql语句
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 生成CacheKey
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    // 调用重载的query()方法
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

在上述

public CacheKey() {
    this.hashcode = DEFAULT_HASHCODE;
    this.multiplier = DEFAULT_MULTIPLIER;
    this.count = 0;
    this.updateList = new ArrayList();
}

同时hashcodechecksumcountupdateList字段会在

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

主要逻辑就是基于

public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, 
                               RowBounds rowBounds, BoundSql boundSql) {
    ......
    
    // 创建CacheKey
    CacheKey cacheKey = new CacheKey();
    
    // 基于MappedStatement的id更新CacheKey
    cacheKey.update(ms.getId());
    // 基于RowBounds的offset更新CacheKey
    cacheKey.update(rowBounds.getOffset());
    // 基于RowBounds的limit更新CacheKey
    cacheKey.update(rowBounds.getLimit());
    // 基于Sql语句更新CacheKey
    cacheKey.update(boundSql.getSql());
    
    ......
    
    // 基于查询参数更新CacheKey
    cacheKey.update(value);
    
    ......
    
    // 基于Environment的id更新CacheKey
    cacheKey.update(configuration.getEnvironment().getId());
    
    return cacheKey; 
}

所以可以得出结论,判断

@Override
public  List 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.");
    }
    // queryStack是BaseExecutor的成员变量
    // queryStack主要用于递归调用query()方法时防止一级缓存被清空
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
        clearLocalCache();
    }
    List list;
    try {
        queryStack++;
        // 先从一级缓存中根据CacheKey命中查询结果
        list = resultHandler == null ? (List) 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 (BaseExecutor.DeferredLoad deferredLoad : deferredLoads) {
            deferredLoad.load();
        }
        deferredLoads.clear();
        // 如果一级缓存作用范围是STATEMENT时,每次query()执行完毕就需要清空一级缓存
        if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            clearLocalCache();
        }
    }
    return list;
}

上述

private  List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, 
                    ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
        // 调用doQuery()进行查询操作
        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;
}

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

所以

上述配置文件中还将一级缓存的作用范围设置为了STATEMENT,目的是为了在例子中屏蔽一级缓存对查询结果的干扰。映射接口如下所示。

public interface BookMapper {

    Book selectBookById(int id);
    void updateBookPriceById(@Param("id") int id, @Param("bookPrice") float bookPrice);

}

要使用二级缓存,还需要在映射文件中加入二级缓存相关的设置,如下所示。


        UPDATE
        book
        SET
        b_price=#{bookPrice}
        WHERE
        id=#{id}
    

二级缓存相关设置的每一项的含义,会在本小节末尾进行说明。

场景一:创建两个会话,会话1以相同

public class MybatisTest {

    public static void main(String[] args) throws Exception {
        String resource = "mybatis-config.xml";
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
                .build(Resources.getResourceAsStream(resource));
        SqlSession sqlSession1 = sqlSessionFactory.openSession(false);
        SqlSession sqlSession2 = sqlSessionFactory.openSession(false);
        BookMapper bookMapper1 = sqlSession1.getMapper(BookMapper.class);
        BookMapper bookMapper2 = sqlSession2.getMapper(BookMapper.class);

        System.out.println(bookMapper1.selectBookById(1));
        System.out.println(bookMapper1.selectBookById(1));

        System.out.println(bookMapper2.selectBookById(1));
    }

}

执行结果如下所示。

public class MybatisTest {

    public static void main(String[] args) throws Exception {
        String resource = "mybatis-config.xml";
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
                .build(Resources.getResourceAsStream(resource));
        SqlSession sqlSession1 = sqlSessionFactory.openSession(false);
        SqlSession sqlSession2 = sqlSessionFactory.openSession(false);
        BookMapper bookMapper1 = sqlSession1.getMapper(BookMapper.class);
        BookMapper bookMapper2 = sqlSession2.getMapper(BookMapper.class);

        System.out.println(bookMapper1.selectBookById(1));
        sqlSession1.commit();
        System.out.println(bookMapper1.selectBookById(1));

        System.out.println(bookMapper2.selectBookById(1));
    }

}

执行结果如下所示。

场景二中第一次查询后提交了事务,此时将查询结果缓存到了二级缓存,所以后续的查询全部在二级缓存中命中了查询结果。

场景三:创建两个会话,会话1执行一次查询并提交事务,然后会话2执行一次更新并提交事务,接着会话1再执行一次相同的查询。执行代码如下所示。

public class MybatisTest {

    public static void main(String[] args) throws Exception {
        String resource = "mybatis-config.xml";
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
                .build(Resources.getResourceAsStream(resource));
        // 将事务隔离级别设置为读已提交
        SqlSession sqlSession1 = sqlSessionFactory.openSession(
            TransactionIsolationLevel.READ_COMMITTED);
        SqlSession sqlSession2 = sqlSessionFactory.openSession(
            TransactionIsolationLevel.READ_COMMITTED);
        BookMapper bookMapper1 = sqlSession1.getMapper(BookMapper.class);
        BookMapper bookMapper2 = sqlSession2.getMapper(BookMapper.class);

        System.out.println(bookMapper1.selectBookById(1));
        sqlSession1.commit();

        System.out.println("Change database.");
        bookMapper2.updateBookPriceById(1, 20.5f);
        sqlSession2.commit();

        System.out.println(bookMapper1.selectBookById(1));
    }

}

执行结果如下所示。

场景三的执行结果表明,执行更新操作并且提交事务后,会清空二级缓存,执行新增和删除操作也是同理。

场景四:创建两个会话,创建两张表,会话1首先执行一次多表查询并提交事务,然后会话2执行一次更新操作以更新表2的数据并提交事务,接着会话1再执行一次相同的多表查询。创表语句如下所示。

CREATE TABLE book(
    id INT(11) PRIMARY KEY AUTO_INCREMENT,
    b_name VARCHAR(255) NOT NULL,
    b_price FLOAT NOT NULL,
    bs_id INT(11) NOT NULL,
    FOREIGN KEY book(bs_id) REFERENCES bookstore(id)
);

CREATE TABLE bookstore(
    id INT(11) PRIMARY KEY AUTO_INCREMENT,
    bs_name VARCHAR(255) NOT NULL
)

book表和bookstore表中添加如下数据。

INSERT INTO book (b_name, b_price, bs_id) VALUES ("Math", 20.5, 1);
INSERT INTO book (b_name, b_price, bs_id) VALUES ("English", 21.5, 1);
INSERT INTO book (b_name, b_price, bs_id) VALUES ("Water Margin", 30.5, 2);

INSERT INTO bookstore (bs_name) VALUES ("XinHua");
INSERT INTO bookstore (bs_name) VALUES ("SanYou")

创建

@Data
public class BookStore {

    private String id;
    private String bookStoreName;

}

创建

@Data
public class BookDetail {

    private long id;
    private String bookName;
    private float bookPrice;

    private BookStore bookStore;

}

public interface BookMapper {

    Book selectBookById(int id);
    void updateBookPriceById(@Param("id") int id, @Param("bookPrice") float bookPrice);
    BookDetail selectBookDetailById(int id);

}


        UPDATE
        book
        SET
        b_price=#{bookPrice}
        WHERE
        id=#{id}
    

还需要添加

public interface BookStoreMapper {

    void updateBookPriceById(@Param("id") int id, @Param("bookStoreName") String bookStoreName);

}

还需要添加


        UPDATE
        bookstore
        SET
        bs_name=#{bookStoreName}
        WHERE
        id=#{id}
    

进行完上述更改之后,进行场景四的测试,执行代码如下所示。

public class MybatisTest {

    public static void main(String[] args) throws Exception {
        String resource = "mybatis-config.xml";
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
                .build(Resources.getResourceAsStream(resource));
        // 将事务隔离级别设置为读已提交
        SqlSession sqlSession1 = sqlSessionFactory.openSession(
                TransactionIsolationLevel.READ_COMMITTED);
        SqlSession sqlSession2 = sqlSessionFactory.openSession(
                TransactionIsolationLevel.READ_COMMITTED);
        BookMapper bookMapper1 = sqlSession1.getMapper(BookMapper.class);
        BookStoreMapper bookStoreMapper = sqlSession2.getMapper(BookStoreMapper.class);

        System.out.println(bookMapper1.selectBookDetailById(1));
        sqlSession1.commit();

        System.out.println("Change database.");
        bookStoreMapper.updateBookStoreById(1, "ShuXiang");
        sqlSession2.commit();

        System.out.println(bookMapper1.selectBookDetailById(1));
    }

}

执行结果如下所示。

会话1第一次执行多表查询并提交事务时,将查询结果缓存到了二级缓存中,然后会话2对bookstore表执行了更新操作并提交了事务,但是最后会话1第二次执行相同的多表查询时,却从二级缓存中命中了查询结果,最终导致查询出来了脏数据。实际上,二级缓存的作用范围是同一命名空间下的多个会话共享,这里的命名空间就是映射文件的namespace,可以理解为每一个映射文件持有一份二级缓存,所有会话在这个映射文件中的所有操作,都会共享这个二级缓存。所以场景四的例子中,会话2对bookstore表执行更新操作并提交事务时,清空的是


        UPDATE
        bookstore
        SET
        bs_name=#{bookStoreName}
        WHERE
        id=#{id}
    

执行代码如下所示。

public class MybatisTest {

    public static void main(String[] args) throws Exception {
        String resource = "mybatis-config.xml";
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
                .build(Resources.getResourceAsStream(resource));
        // 将事务隔离级别设置为读已提交
        SqlSession sqlSession1 = sqlSessionFactory.openSession(
                TransactionIsolationLevel.READ_COMMITTED);
        SqlSession sqlSession2 = sqlSessionFactory.openSession(
                TransactionIsolationLevel.READ_COMMITTED);
        BookMapper bookMapper1 = sqlSession1.getMapper(BookMapper.class);
        BookStoreMapper bookStoreMapper = sqlSession2.getMapper(BookStoreMapper.class);

        System.out.println(bookMapper1.selectBookDetailById(1));
        sqlSession1.commit();

        System.out.println("Change database.");
        bookStoreMapper.updateBookStoreById(1, "ShuXiang");
        sqlSession2.commit();

        System.out.println(bookMapper1.selectBookDetailById(1));
    }

}

执行结果如下所示。

private void configurationElement(XNode context) {
    try {
        String namespace = context.getStringAttribute("namespace");
        if (namespace == null || namespace.isEmpty()) {
            throw new BuilderException("Mapper's namespace cannot be empty");
        }
        builderAssistant.setCurrentNamespace(namespace);
        // 解析标签
        cacheRefElement(context.evalNode("cache-ref"));
        // 解析标签
        cacheElement(context.evalNode("cache"));
        parameterMapElement(context.evalNodes("/mapper/parameterMap"));
        resultMapElements(context.evalNodes("/mapper/resultMap"));
        sqlElement(context.evalNodes("/mapper/sql"));
        buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
        throw new BuilderException("Error parsing Mapper XML. The XML location is '" 
                + resource + "'. Cause: " + e, e);
    }
}

private void cacheElement(XNode context) {
    if (context != null) {
        // 获取标签的type属性值
        String type = context.getStringAttribute("type", "PERPETUAL");
        Class extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
        // 获取标签的eviction属性值
        String eviction = context.getStringAttribute("eviction", "LRU");
        Class extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
        // 获取标签的flushInterval属性值
        Long flushInterval = context.getLongAttribute("flushInterval");
        // 获取标签的size属性值
        Integer size = context.getIntAttribute("size");
        // 获取标签的readOnly属性值并取反
        boolean readWrite = !context.getBooleanAttribute("readOnly", false);
        // 获取标签的blocking属性值
        boolean blocking = context.getBooleanAttribute("blocking", false);
        Properties props = context.getChildrenAsProperties();
        builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
}

单步跟踪

public Cache useNewCache(Class extends Cache> typeClass,
                         Class extends Cache> evictionClass,
                         Long flushInterval,
                         Integer size,
                         boolean readWrite,
                         boolean blocking,
                         Properties props) {
    Cache cache = new CacheBuilder(currentNamespace)
            .implementation(valueOrDefault(typeClass, PerpetualCache.class))
            .addDecorator(valueOrDefault(evictionClass, LruCache.class))
            .clearInterval(flushInterval)
            .size(size)
            .readWrite(readWrite)
            .blocking(blocking)
            .properties(props)
            .build();
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
}

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

所以可以知道,

public Cache build() {
    setDefaultImplementations();
    // 创建PerpetualCache,作为基础Cache对象
    Cache cache = newBaseCacheInstance(implementation, id);
    setCacheProperties(cache);
    if (PerpetualCache.class.equals(cache.getClass())) {
        // 为基础Cache对象添加缓存淘汰策略相关的装饰器
        for (Class extends Cache> decorator : decorators) {
            cache = newCacheDecoratorInstance(decorator, cache);
            setCacheProperties(cache);
        }
        // 继续添加装饰器
        cache = setStandardDecorators(cache);
    } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
        cache = new LoggingCache(cache);
    }
    return cache;
}

那么生成的二级缓存对象如下所示。

整个装饰链如下图所示。

现在回到

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

这里就印证了前面的猜想,即二级缓存

private void cacheRefElement(XNode context) {
    if (context != null) {
        // 在Configuration的cacheRefMap中将当前映射文件命名空间与引用的映射文件命名空间建立映射关系
        configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
        CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
        try {
            // CacheRefResolver会将引用的映射文件的二级缓存从Configuration中获取出来并赋值给MapperBuilderAssistant的currentCache
            cacheRefResolver.resolveCacheRef();
        } catch (IncompleteElementException e) {
            configuration.addIncompleteCacheRef(cacheRefResolver);
        }
    }
}

@Override
public  List query(MappedStatement ms, Object parameterObject, 
        RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 获取Sql语句
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 创建CacheKey
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

继续看重载的

@Override
public  List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, 
              ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    // 从MappedStatement中将二级缓存获取出来
    Cache cache = ms.getCache();
    if (cache != null) {
        // 清空二级缓存(如果需要的话)
        flushCacheIfRequired(ms);
        if (ms.isUseCache() && resultHandler == null) {
            // 处理存储过程相关逻辑
            ensureNoOutParams(ms, boundSql);
            // 从二级缓存中根据CacheKey命中查询结果
            List list = (List) tcm.getObject(cache, key);
            if (list == null) {
                // 未命中缓存,则查数据库
                list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                // 将从数据库查询到的结果缓存到二级缓存中
                tcm.putObject(cache, key, list);
            }
            // 返回查询结果
            return list;
        }
    }
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

上述

public Object getObject(Cache cache, CacheKey key) {
    return getTransactionalCache(cache).getObject(key);
}

private TransactionalCache getTransactionalCache(Cache cache) {
    return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
}

通过上述代码可以知道,一个二级缓存对应一个

@Override
public Object getObject(Object key) {
    // 在二级缓存中命中查询结果
    Object object = delegate.getObject(key);
    if (object == null) {
        // 未命中则将CacheKey添加到entriesMissedInCache中
        // 用于统计命中率
        entriesMissedInCache.add(key);
    }
    if (clearOnCommit) {
        return null;
    } else {
        return object;
    }
}

到这里就可以知道了,在

private void flushCacheIfRequired(MappedStatement ms) {
    Cache cache = ms.getCache();
    if (cache != null && ms.isFlushCacheRequired()) {
        tcm.clear(cache);
    }
}

调用

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

现在继续分析为什么将查询结果缓存到二级缓存中需要事务提交。从数据库中查询出来结果后,

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

继续看

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

到这里就搞明白了,在事务提交之前,查询结果会被暂存

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

@Override
public void commit(boolean required) throws SQLException {
    delegate.commit(required);
    // 调用TransactionalCacheManager的commit()方法
    tcm.commit();
}

public void commit() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
        // 调用TransactionalCache的commit()方法
        txCache.commit();
    }
}

继续看

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

private void flushPendingEntries() {
    // 将entriesToAddOnCommit中暂存的查询结果全部缓存到二级缓存中
    for (Map.Entry entry : entriesToAddOnCommit.entrySet()) {
        delegate.putObject(entry.getKey(), entry.getValue());
    }
    for (Object entry : entriesMissedInCache) {
        if (!entriesToAddOnCommit.containsKey(entry)) {
            delegate.putObject(entry, null);
        }
    }
}

至此可以知道,当调用

boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);

即如果没有在CURD标签中显式的设置flushCache属性,则会给flushCache字段一个默认值,且默认值为非查询标签下默认为true,所以到这里就可以知道,如果是操作,那么

TransactionalCache
中的clearOnCommit字段会被置为true,从而在提交事务时会在
TransactionalCache
commit()
方法中将二级缓存清空。

到这里,二级缓存的源码分析结束。二级缓存的使用流程可以用下图进行概括,如下所示。

总结

关于

Mybatis
的一级缓存,总结如下。
  • Mybatis
    的一级缓存默认开启,且默认作用范围为SESSION,即一级缓存在一个会话中生效,也可以通过配置将作用范围设置为STATEMENT,让一级缓存仅针对当前执行的
    SQL
    语句生效;
  • 在同一个会话中,执行操作会使本会话中的一级缓存失效;
  • 不同会话持有不同的一级缓存,本会话内的操作不会影响其它会话内的一级缓存。

关于

Mybatis
的二级缓存,总结如下。
  • Mybatis
    中的二级缓存默认开启,可以在
    Mybatis
    配置文件中的
    中添加
    将二级缓存关闭;
  • Mybatis
    中的二级缓存作用范围是同一命名空间下的多个会话共享,这里的命名空间就是映射文件的namespace,即不同会话使用同一映射文件中的
    SQL
    语句对数据库执行操作并提交事务后,均会影响这个映射文件持有的二级缓存;
  • Mybatis
    中执行查询操作后,需要提交事务才能将查询结果缓存到二级缓存中;
  • Mybatis
    中执行增,删或改操作并提交事务后,会清空对应的二级缓存;
  • Mybatis
    中需要在映射文件中添加
    标签来为映射文件配置二级缓存,也可以在映射文件中添加
    标签来引用其它映射文件的二级缓存以达到多个映射文件持有同一份二级缓存的效果。

好了,本文到此结束,带大家了解了《Mybatis源码-缓存机制》,希望本文对你有所帮助!关注golang学习网公众号,给大家分享更多数据库知识!

声明:本文转载于:SegmentFault 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>
评论列表