专栏首页后端开发随笔深入浅出mybatis之缓存机制

深入浅出mybatis之缓存机制

前言

提到缓存,我们都会不约而同地认识到这是提高系统性能的必要措施之一,特别是高命中率的缓存设置,将会大大提高系统的整体吞吐量。缓存的应用场景从小到在http会话中缓存登录信息,大到为数据库分担一部分查询压力的独立缓存组件(如Redis,Memcached等),应用都非常普遍。而MyBatis作为Java中非常流行的ORM组件,也不可免俗地使用了缓存机制。那么我们不禁要提出疑问:MyBatis是如何实现缓存的?如何在应用程序中合理地使用MyBatis缓存?如下内容基于MyBatis3.4.5版本进行说明。

准备工作

  • 数据库表
create table `student` (
  `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `name` VARCHAR(50) default '',
  `age` INT NOT NULL DEFAULT 0 COMMENT '年龄',
  `sex` TINYINT NOT NULL DEFAULT 0 COMMENT '性别,0:男,1:女',
  `ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `mtime` timestamp  DEFAULT CURRENT_TIMESTAMP COMMENT '编辑时间',
  PRIMARY KEY (`id`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
  • 接口映射器
public interface StudentMapper {
    // 缓存的应用主要是查询的场景
    @Select("select * from student where id = #{id}")
    Student getStudentById(@Param("id") long id);
}
  • XML映射器
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.chench.test.mybatis.mapper">
    <!-- 查询一条数据 -->
    <select id="getStudentById" resultType="org.chench.test.mybatis.model.Student">
        select * from test where id = #{id}
    </select>
</mapper>

MyBatis默认缓存设置

在MyBatis中,关于缓存设置的参数一共有2个:localCacheScope,cacheEnabled。

<!-- 有效值: true|false,默认值为true -->
<setting name="cacheEnabled" value="true" />
<!-- 有效值:SESSION|STATEMENT,默认值为SESSION -->
<setting name="localCacheScope" value="SESSION" />

那么这两个参数分别在什么地方使用呢?不妨先走读一下MyBatis的相关源码。

首先,来看看参数cacheEnabled的应用。

  • org.apache.ibatis.session.defaults.DefaultSqlSessionFactory
// 在应用程序中通过sqlSessionFactory获取一个SqlSession对象执行CRUD操作
SqlSession sqlSession = sqlSessionFactory.openSession(true);

// 在DefaultSqlSessionFactory中获取SqlSession对象
@Override
public SqlSession openSession(boolean autoCommit) {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);
}

// 通过MyBatis配置参数构建SqlSession对象
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);
        // 根据配置的Executor类型装配具体的实现类
        final Executor executor = configuration.newExecutor(tx, execType);
        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();
    }
}
  • org.apache.ibatis.session.Configuration
// 在Configuration中根据不同的defaultExecutorType参数值装配具体的Executor实现
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    // 根据不同的defaultExecutorType参数值装配具体的Executor实现
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
        // 当defaultExecutorType值为BATCH时,使用BatchExecutor
        executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
        // 当defaultExecutorType值为REUSE时,使用ReuseExecutor
        executor = new ReuseExecutor(this, transaction);
    } else {
        // 默认情况下使用SimpleExecutor
        executor = new SimpleExecutor(this, transaction);
    }
    // 如果设置cacheEnabled参数值为true,将使用CachingExecutor
    if (cacheEnabled) {
        executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
}

从上述源码中可以看到,MyBatis会根据配置参数defaultExecutorType的值使用不同的执行器:BatchExecutor,ReuseExecutor,SimpleExecutor。此外,当参数cacheEnabled值为true时,会使用一个特别的执行器:CachingExecutor。那么,不同的执行器有什么不同呢?他们有什么联系吗?下图为MyBatis中执行器的类图。

其次,再来跟踪一下参数localCacheScope的使用场景。 如下为MyBatis执行查询的时序图:

从查询时序图中可以看到,MyBatis中的查询操作会通过执行器来完成,因此我们需要跟踪一下相关执行器的源码。

  • org.apache.ibatis.executor.BaseExecutor
@SuppressWarnings("unchecked")
@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 (BaseExecutor.DeferredLoad deferredLoad : deferredLoads) {
            deferredLoad.load();
        }
        // issue #601
        deferredLoads.clear();
        // 如果参数localCacheScope值为STATEMENT,则每次查询之后都清空缓存
        if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            // issue #482
            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 {
        // 真正执行数据库查询操作的是BaseExecutor子类中实现的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;
}

// BaseExecutor中的doQuery()方法是一个抽放方法
// 所以真正执行数据库查询的操作都是委托给了BaseExecutor子类:BatchExecutor,ReuseExecutor和SimpleExecutor
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
            throws SQLException;

从BaseExecutor的query()方法中可以看到,在执行数据查询时会先尝试从本地名为“localCache”的缓存对象中获取数据,当从缓存中未查询到数据时才直接从数据库查询。另外,当参数localCacheScope值为STATEMENT时,每次查询之后都会清空BaseExecutor的本地缓存。

OK,到这里我们就可以对MyBatis中控制缓存的2个参数做一个浅显的总结: (1)参数cacheEnabled控制MyBatis使用的执行器类型 (2)参数localCacheScope控制的是BaseExecutor内部的缓存策略

缓存实现原理分析

既然在MyBatis中是通过cacheEnabled和localCacheScope这2个参数来控制缓存的,那么如下的实现原理分析也基于这2个参数进行。

参数localCacheScope控制的缓存策略

在MyBatis的默认缓存设置中我们已经知道,参数cacheEnabled控制的是不同的执行器类型,而从MyBatis的执行器类图中又可以看出,当参数cacheEnabled为false时,MyBatis使用的执行器类型为BaseExecutor。并且,从上述对BaseExecutor中query()方法源码中也可以看到,在BaseExecutor中会使用一个名为“localCache”的缓存对象缓存查询数据。参数localCacheScope的有效值分别为SESSION,STATEMENT,当该参数值为STATEMENT时,每次查询操作都会清空BaseExecutor中的本地缓存。因此,在这里我们需要深入分析一下,BaseExecutor中的本地缓存实现机制是什么。 实际上,BaseExecutor中的本地缓存是一个org.apache.ibatis.cache.impl.PerpetualCache类型的实例,跟踪其源码可以发现,其内部仅仅是封装了一个HashMap对象,真正缓存的数据正是存放在这个HashMap实例中的。

public class PerpetualCache implements Cache {
    private final String id;
    private Map<Object, Object> cache = new HashMap<Object, Object>();

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

而且,从BaseExecutor的query()方法中可以看到,每次从本地缓存对象中取数据时的Key是一个类型为org.apache.ibatis.cache.CacheKey的实例。另外,从HashMap的实现原理我们也清楚,HashMap内部认为两个对象的Key是否相同需要满足如下条件: 第一,两个Key的hashCode值必须相同,这是前提; 第二,两个Key引用的对象相同或者他们通过equals()方法比较时返回true。

也就说,如果要使得BaseExecutor内部的本地缓存生效,必须保证查询时传入的CacheKey对象满足HashMap内部判断Key相同的条件,否则无法命中缓存。那么,我们来继续跟踪一下这个CacheKey的实现。

  • org.apache.ibatis.cache.CacheKey
public class CacheKey implements Cloneable, Serializable {
    private final int multiplier;
    private int hashcode;
    private long checksum;
    private int count;
    private transient List<Object> updateList;
    
    @Override
    public int hashCode() {
        // hashCode值为内部保存的hashcode属性
        return hashcode;
    }
    
    @Override
    public boolean equals(Object object) {
        // 判断两个CacheKey实例通过equals()方法比较时返回true必须同时满足如下条件:
        // 1.hashcode属性值相等
        // 2.checksum属性值相等
        // 3.count属性值相等
        // 4.updateList列表属性中存放的每一个对象通过equals()方法比较时返回true
        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;
    }
}

在CacheKey内部存放了4个关键属性:hashcode,checksum,count和updateList,他们的值影响比较两个CacheKey对象时是否相同。也就说,为了使得BaseExecutor内部的本地缓存被命中,必须使得查询时传递的CacheKey对象中对应的属性值与存放缓存数据时设置的CacheKey中的属性相同。 那么,我们就需要跟踪一下CacheKey是如何被构造的。

  • org.apache.ibatis.executor.BaseExecutor
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 在查询之前构造CacheKey缓存Key对象
    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()方法将一个对象放在其内部的updateList列表中
    // 只要是查询同一条数据的相同SQL语句,可以保证如下参数的相同的:
    // ms.getId(),rowBounds.getOffset(),rowBounds.getLimit(),boundSql.getSql(),boundSql.getParameterMappings()
    // 也就是说,只要是在相同的SqlSession中查询同一条数据时都会命中BaseExecutor的本地缓存
    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;
}

从上述构造BaseExecutor本地缓存对象的CacheKey源码分析中可以得出这样的结论:在相同的SqlSession中查询同一条数据时都会命中BaseExecutor的本地缓存。也就是说通过参数localCacheScope控制的缓存策略只能在相同SqlSession内有效,因为BaseExecutor的本地缓存对象localCache是实例属性,在不同的执行器实例中都保存一个独立的本地缓存,而在不同的SqlSession中使用的是不同的执行器实例。这个关系可以通过下图描述:

那么,到底是不是这样的呢?我们需要进行验证。

<!-- 为了验证BaseExecutor内的缓存策略,需要设置cacheEnabled参数为false,默认值为true -->
<setting name="cacheEnabled" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
// 在相同Session中查询同一条数据
SqlSession sqlSession = sqlSessionFactory.openSession(true);
Student student = sqlSession.getMapper(StudentMapper.class).getStudentById(1);
System.out.println(student);
student = sqlSession.getMapper(StudentMapper.class).getStudentById(1);
System.out.println(student);
sqlSession.close();

对应MyBatis输出日志如下:

method: query
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 1131184547.
DEBUG [main] - ==>  Preparing: select * from student where id = ? 
DEBUG [main] - ==> Parameters: 1(Long)
DEBUG [main] - <==      Total: 1
Student{id=1, name='张三', age=23, sex=0}
method: query
Student{id=1, name='张三', age=23, sex=0}
DEBUG [main] - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@436c81a3]
DEBUG [main] - Returned connection 1131184547 to pool.

显然,从输出日志中可以很确定地知道:在相同Session中查询同一条数据时,只有第一次会真正从数据库中查询,后续的查询都会直接从Session内的缓存中获取。而且,我们从上述相关源码中知道,只要SqlSession存在,该缓存是永远存在,不会失效。

// 在不同Session中查询同一条数据
SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
Student student = sqlSession1.getMapper(StudentMapper.class).getStudentById(1);
System.out.println(student);
sqlSession1.close();

SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
student = sqlSession2.getMapper(StudentMapper.class).getStudentById(1);
System.out.println(student);
sqlSession2.close();

而在不同的Session中查询同一条数据时,都分别从数据库直接查询,如下输出日志所示:

method: query
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 1131184547.
DEBUG [main] - ==>  Preparing: select * from student where id = ? 
DEBUG [main] - ==> Parameters: 1(Long)
DEBUG [main] - <==      Total: 1
Student{id=1, name='张三', age=23, sex=0}
DEBUG [main] - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@436c81a3]
DEBUG [main] - Returned connection 1131184547 to pool.
method: query
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Checked out connection 1131184547 from pool.
DEBUG [main] - ==>  Preparing: select * from student where id = ? 
DEBUG [main] - ==> Parameters: 1(Long)
DEBUG [main] - <==      Total: 1
Student{id=1, name='张三', age=23, sex=0}
DEBUG [main] - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@436c81a3]
DEBUG [main] - Returned connection 1131184547 to pool.

参数cacheEnabled控制的缓存策略

在了解了参数localCacheScope控制的缓存策略之后,还需要继续研究参数cacheEnabled所控制的缓存策略。从上述源码分析中已经知道,当参数cacheEnabled值为true时,MyBatis将使用CachingExecutor执行器,下面通过源码解读一下CachingExecutor到底与其他Executor实现类有什么不同。

  • org.apache.ibatis.executor.CachingExecutor
// 被包装的执行器对象
private final Executor delegate;

// CachingExecutor内部的缓存管理器
private final TransactionalCacheManager tcm = new TransactionalCacheManager();

// 在构造函数中传入一个执行器对象
public CachingExecutor(Executor delegate) {
    this.delegate = delegate;
    delegate.setExecutorWrapper(this);
}

// CachingExecutor实现的查询方法,在这里实现缓存
@Override
public <E> List<E> 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, parameterObject, boundSql);
            @SuppressWarnings("unchecked")
            List<E> list = (List<E>) tcm.getObject(cache, key);
            if (list == null) {
                list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                tcm.putObject(cache, key, list); // issue #578 and #116
            }
            return list;
        }
    }
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

从CachingExecutor的源码走读中可以得到如下信息: (1)CachingExecutor内部使用了一个独立的缓存管理组件TransactionalCacheManager,其实现如下:

public class TransactionalCacheManager {
    // 内部是一个HashMap,所以TransactionalCacheManager本身不负责缓存数据
    // 值得注意的是:该HashMap的Key和Value都是Cache类型,实际上真正缓存数据的是Value对应的TransactionalCache实例
    private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();
}

该缓存管理器的名称很有意思,从字面上看似乎与事务相关。实际上正是因为在事务关闭的时候会调用该缓存管理器的commit()方法,从而实现了通过参数cacheEnabled控制的缓存策略是全局的,这是一个非常巧妙的设计。 (2)在CachingExecutor的query()方法中可以看到,在执行数据查询时,先通过它的缓存管理器查询缓存,如果从缓存中没有取到数据时,将使用具体的执行器查询数据并缓存(在CachingExecutor中包装的具体执行器实际上就是BaseExecutor类型的实例,如:BatchExecutor,ReuseExecutor或SimpleExecutor)。 (3)CachingExecutor的缓存管理器在缓存数据时使用的Key是一个从MappedStatement中获取的Cache实例对象,实际上,在MappedStatement内部确实存在一个Cache类型实例的属性,继续解读相关源码之后才知道,原来这个Cache类型的对象需要在MyBatis的映射器中配置,并且该实例对象是全局的。实际上,这一点也从MyBatis的xml映射器配置文档中得到了证实。

再一次仔细阅读CachingExecutor的query()方法实现,将其缓存原理通过下图进行总结。

第一步,缓存数据时,将数据临时保存到TransactionalCacheManager中属性transactionalCaches的Value所引用的TransactionalCache实例内部的HashMap中。 第二步,事务关闭时将TransactionalCache实例内部HashMap中临时保存的数据全部转移到全局的Cache实例中。 第三步,从缓存中取数据时实际上直接从全局Cache实例中查询。

因为数据最终保存到了全局的Cache实例中,所以说参数cacheEnabled控制的缓存策略是全局的(属于应用上下文范围),在不同的Session中查询同一条数据时都会从这个全局缓存中查询,下面通过实例来进行验证。

  • 打开全局缓存开关
<!-- 打开全局缓存开关 -->
<setting name="cacheEnabled" value="true"/>
<!--  为了验证全局缓存,在这里把Session缓存关闭 -->
<setting name="localCacheScope" value="STATEMENT"/>
  • 定义全局缓存实例
// 在xml映射器中定义全局缓存
<!-- 在xml映射器中配置全局缓存 -->
<cache />

在xml映射器中配置全局缓存很简单,只需要在xml映射器中简单添加一个<cache />节点即可,这里为了演示全局缓存的效果,所以不用配置详细参数,使用默认值即可。

// 在接口映射器中定义全局缓存
@CacheNamespace
public interface StudentMapper {

在接口映射器中配置全局缓存通过注解@CacheNamespace实现,其效果与在xml映射器中通过节点<cache />配置是一样的。

  • 验证全局缓存的作用

通过参数cacheEnabled控制的缓存是全局的,所以在多个Session中使用相同SQL语句查询同一条数据时,只在第一次查询时直接查询数据库,之后的查询都会从这个全局缓存中读取数据。如下以通过xml映射器查询为例:

// 在不同Session中查询同一条数据
SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
Student student = sqlSession1.selectOne("org.chench.test.mybatis.mapper.getStudentById", 1);
System.out.println(student);
sqlSession1.close();

SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
student = sqlSession2.selectOne("org.chench.test.mybatis.mapper.getStudentById", 1);
System.out.println(student);
sqlSession2.close();

查看MyBatis的输出日志:

method: query
DEBUG [main] - Cache Hit Ratio [org.chench.test.mybatis.mapper]: 0.0
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 1463355115.
DEBUG [main] - ==>  Preparing: select * from test where id = ? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
Student{id=1, name='1509690042107_haha_update_update', age=0, sex=0}
DEBUG [main] - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@573906eb]
DEBUG [main] - Returned connection 1463355115 to pool.
method: query
DEBUG [main] - Cache Hit Ratio [org.chench.test.mybatis.mapper]: 0.5
Student{id=1, name='1509690042107_haha_update_update', age=0, sex=0}

显然,从日志中很明显看到第一次查询时缓存命中率为0,第二次查询时缓存命中率为0.5,直接从缓存中取得了数据。

总结

MyBatis的缓存功能同时受到localCacheScope和cacheEnabled这2个运行时参数的控制。那么我们不禁要问:为什么需要使用2个参数进行控制而不是直接使用1个参数更加简洁?实际上,2个参数控制的缓存策略是不一样,localCacheScope参数控制的缓存是Session范围内的,称为一级缓存;而cacheEnabled参数控制的缓存是全局的,称为二级缓存,这对应于不同的应用需求。

显然,MyBatis的默认配置是同时开启了Session缓存和全局缓存。另外请注意:cacheEnabled参数仅仅是打开了全局缓存开关,但这并不意味着默认情况下MyBatis就会进行全局缓存。实际上,如果需要使用全局缓存,还必须在映射器中配置全局缓存实例。

【参考】 [1]. http://www.mybatis.org/mybatis-3/zh/configuration.html#settings [2]. http://mp.weixin.qq.com/s/Ju4d71VrL0omGkV3s3U_1Q MyBatis缓存机制 [3]. http://www.cnblogs.com/yjmyzz/p/use-cache-in-mybatis.html mybatis 3.x 缓存Cache的使用

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 细说shiro之七:缓存

    Shiro作为一个开源的权限框架,其组件化的设计思想使得开发者可以根据具体业务场景灵活地实现权限管理方案,权限粒度的控制非常方便。 首先,我们来看看Shiro...

    2Simple
  • 细说tomcat之类加载器

    官网:http://tomcat.apache.org/tomcat-7.0-doc/class-loader-howto.html Java类加载与Tomca...

    2Simple
  • eclipse控制台中文乱码解决方法

    2.General > Workspace > Text file encoding.

    2Simple
  • 前端缓存那些事

    Cache-Control 比Expires比较要内涵,具备更多的属性,其中包括如下

    树酱
  • 前端缓存那些事

    Cache-Control 比Expires比较要内涵,具备更多的属性,其中包括如下

    树酱
  • Guava CacheBuilder使用说明 原

    CacheBuilder是Guava用于创建LoadingCache、Cache实例的构建类。可以使用下面的方法来创建一个Cache实例。

    随风溜达的向日葵
  • 缓存的Cache Aside模式

    codecraft
  • 缓存穿透,缓存击穿,缓存雪崩的内容和解决方案

    每次想到缓存的概念时就会想到下面这张结构图,缓存主要解决的是中央处理器与内存之间速度不匹配出来的问题。

    后端Coder
  • ABP入门系列(13)——Redis缓存用起来

    源码路径:Github-LearningMpaAbp 1. 引言 创建任务时我们需要指定分配给谁,Demo中我们使用一个下拉列表用来显示当前系统的所有用户,以...

    圣杰
  • 对缓存的思考——提高命中率

    开篇 编写高效的程序并不只在于算法的精巧,还应该考虑到计算机内部的组织结构,cpu微指令的执行,缓存的组织和工作原理等。 好的算法在实际中不见得有高效率,如果完...

    猿人谷

扫码关注云+社区

领取腾讯云代金券