注:本系列源码分析基于mybatis 3.5.6,源码的gitee仓库仓库地址:funcy/mybatis.
本文将从源码的角度来分析mybatis的缓存。
在前面分析mybatis
的sql执行流程时,CachingExecutor
一路执行下去会遇到两个缓存:
CachingExecutor#query(...)
执行时,会先判断缓存中是否存在,不存在时才去调用具体的Executor
查询BaseExecutor#query(...)
执行时,会先从localCache
中获取,不存在时才从数据库中查询
我们还是以上一文中的sql执行过程为例,逐步分析这两个缓存。
1. mybatis
一级缓存
让我们进入BaseExecutor#query(...)
方法:
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
...
List<E> list;
try {
queryStack++;
// 1. 直接从缓存中获取数据
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();
// 2. 缓存范围为STATEMENT时,会清除缓存
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
// 清除数据库中的记录
clearLocalCache();
}
}
return list;
}
复制代码
这个方法有点长,不过我们只需要关注两个地方即可:
- 直接从缓存中(
localCache
)获取数据 - 缓存范围为
STATEMENT
时,每次查询后会清除缓存,即数据不会被缓存
1.1 什么是一级缓存
首先我们来看下localCache
是个啥:
public abstract class BaseExecutor implements Executor {
...
/**
* 这就是 localCache
*/
protected PerpetualCache localCache;
protected BaseExecutor(Configuration configuration, Transaction transaction) {
...
// 在这里赋值
this.localCache = new PerpetualCache("LocalCache");
}
...
复制代码
这个localCache
就是PerpetualCache
,我们继续:
public class PerpetualCache implements Cache {
private final String id;
private final Map<Object, Object> cache = new HashMap<>();
public PerpetualCache(String id) {
this.id = 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();
}
...
}
复制代码
PerpetualCache
就是包装了Map
的处理,缓存的添加、获取、移除等操作,实际上就是对Map
的操作。
到这里我们就明白了,所谓的一级缓存(localCache
),就是一个Map
,记录都是保存在内存中的。注意:这里的Map
是HashMap
,是非线程安全的。
1.2 一级缓存的更新时机
在BaseExecutor#query(...)
方法中,如果没命中缓存,则会从数据库中查询,我们来看看数据库的查询流程:
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;
}
复制代码
代码可以看的很清楚了,在处理查询(doQuery(...)
)后,会手动移除一级缓存(localCache.removeObject(...)
),并设置一级缓存(localCache.putObject(...)
).
当然了,在update
操作中也有类似的处理缓存的操作,这里就不一一看了。
1.3 一级缓存的作用范围及禁用
一级缓存中,缓存数据的Map
是HashMap
,并非线程安全的,因此一级缓存并非线程安全的。那么它的作用范围呢?
通过代码的追溯,localCache
是Executor
的成员变量,而Executor
又是DefaultSqlSession
的成员变量。在前面的分析中,DefaultSqlSession
并不是线程安全的,比较合理的方式是为每个线程新建一个DefaultSqlSession
,至此可以推断出一级缓存(localCache
)的作用范围为SqlSession
,保存在内存中。
由于一级缓存的作用范围为SqlSession
,因此使用时可能会导致数据库更新了,但缓存还没变。
举例说明下:
// 配置文件路径
String resource = "org/apache/ibatis/demo/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(inputStream);
// 使用 sqlSession01 获取 userMapper
SqlSession sqlSession01 = null;
SqlSession sqlSession02 = null;
try {
sqlSession01 = factory.openSession(true);
sqlSession02 = factory.openSession(true);
UserMapper userMapper01 = sqlSession01.getMapper(UserMapper.class);
// 使用 sqlSession02 获取 userMapper
UserMapper userMapper02 = sqlSession02.getMapper(UserMapper.class);
// 处理查询与更新操作,顺序很重要
// 使用 userMapper01 查询
List<User> users = userMapper01.selectList(3L, 10);
System.out.println("userMapper01 得到的users: " + users);
// 使用 sqlSession02 更新
int result = userMapper02.updateUser(3L, "HelloWorld");
System.out.println("更新的记录数: " + result);
// 使用 sqlSession02 查询
List<User> users2 = userMapper02.selectList(3L, 10);
System.out.println("userMapper02 得到的users: " + users2);
// 使用 sqlSession01 查询
List<User> users01 = userMapper01.selectList(3L, 10);
System.out.println("userMapper01 得到的users: " + users01);
} finally {
// 省略关闭操作
...
}
复制代码
以上代码先是获取了两个SqlSession
,然后分别从这两个SqlSession
得到UserMapper
的实例userMapper01
、userMapper02
,然后我们使用这两个UserMapper
实例进行操作:
- 使用
userMapper01
查询id为3的记录 - 使用
userMapper02
更新id为3的记录 - 使用
userMapper02
查询id为3的记录 - 使用
userMapper01
查询id为3的记录
运行,得到的结果如下:
userMapper01 得到的users: [User{id=3, loginName='test', nick='test'}]
更新的记录数: 1
userMapper02 得到的users: [User{id=3, loginName='test', nick='HelloWorld'}]
userMapper01 得到的users: [User{id=3, loginName='test', nick='test'}]
复制代码
从运行结果来看,userMapper02
更新成功了,userMapper02
也能查到更新后数据了,但userMapper01
第二次查到的依然是更新前的记录,这也就是说,使用userMapper01
第一次查询时,记录被缓存了,第二次查询时,并没有查询数据库,而是直接从缓存里获取数据了。
从以上示例可以看到,开启一级缓存后,并不能查到实时数据,并且一级缓存并没有提供对外操作的入口,启用后极有可能会产生严重后果,那么我们要怎么关闭它呢?
在mybatis文档的settings
节点介绍中,有一个属性可以解决这个问题:
我们在配置文件中这样设置:
<configuration>
<properties resource="org/apache/ibatis/demo/config.properties">
</properties>
<settings>
<!-- 设置一级缓存级别 -->
<setting name="localCacheScope" value="STATEMENT"/>
</settings>
...
</configuration>
复制代码
这样设置之后,一级缓存就不再缓存数据了。关于这样做的原理,就得回到BaseExecutor#query(...)
方法了:
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
...
if (queryStack == 0) {
...
// 2. 缓存范围为STATEMENT时,会清除缓存
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// 清除数据库中的记录
clearLocalCache();
}
}
return list;
}
/**
* 清除缓存的操作
*/
@Override
public void clearLocalCache() {
if (!closed) {
localCache.clear();
localOutputParameterCache.clear();
}
}
复制代码
从源码来看,将localCacheScope
设置为STATEMENT
后,每次查询完成后,都会调用localCache.clear()
来清除缓存了。
2. mybatis
二级缓存
接下来我们再来看看二级缓存。
2.1 开启二级缓存
在创建执行器时,mybatis
在创建完具体的执行器后,会再缓存执行器:
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) {
executor = new CachingExecutor(executor);
}
// 处理 plugin(插件)
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
复制代码
在之后处理sql的查询/更新操作时,都是调用CachingExecutor
进行,比如查询操作:
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds,
ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
Cache cache = ms.getCache();
// 操作缓存
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);
}
复制代码
这里的Cache cache = ms.getCache()
就是用来操作二级缓存的。默认情况下,二级缓存并未开启:
可以看到,cache
为 null
,表示并未启用二级缓存。那么该如何启用呢?mybatis文档告诉了我们启用方式:
我们在UserMapper.xml
中添加<cache/>
标签:
<?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.apache.ibatis.demo.mapper.UserMapper">
<cache/>
...
</mapper>
复制代码
再次执行查询时,就会发现cache
已经不为null了:
2.2 配置二级缓存
在mybatis
文档中,二级缓存还有其他的配置,这里也一并贴出来:
这些参数本文就不细讲了,我们来看下这些参数是在哪里解析的。
<cache/>
标签定义在mapper.xml
文件中,我们当然就想到它应该是在解析mapper.xml
文件时解析到的,我们进入<mapper>
标签的解析方法XMLMapperBuilder#configurationElement
:
private void configurationElement(XNode context) {
try {
...
// 解析二级缓存标签
cacheElement(context.evalNode("cache"));
...
} catch (Exception e) {
...
}
}
复制代码
继续进入XMLMapperBuilder#cacheElement
方法:
private void cacheElement(XNode context) {
if (context != null) {
String type = context.getStringAttribute("type", "PERPETUAL");
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
Long flushInterval = context.getLongAttribute("flushInterval");
Integer size = context.getIntAttribute("size");
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
// 构建Cache对象
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size,
readWrite, blocking, props);
}
}
复制代码
- 缓存的默认实现类为
PerpetualCache
,我们也可以指定type
为我们自己的实现类,下节再说明 - 缓存过期的默认策略为
LruCache
readOnly
默认值为false
blocking
默认值为false
size
默认值为null
flushInterval
默认值为null
我们继续进入MapperBuilderAssistant#useNewCache
看看cache
的构建过程:
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;
}
复制代码
这个方法主要是调用CacheBuilder
来构建Cache
,我们进入CacheBuilder#build
方法:
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);
}
// 设置标准的装饰器,用来处理eviction,flushInterval,size,readOnly等配置
cache = setStandardDecorators(cache);
}
// 如果不是 PerpetualCache,不会处理eviction,flushInterval,size,readOnly等配置
else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
cache = new LoggingCache(cache);
}
return cache;
}
/**
* 设置缓存的默认实现类
*/
private void setDefaultImplementations() {
if (implementation == null) {
implementation = PerpetualCache.class;
if (decorators.isEmpty()) {
decorators.add(LruCache.class);
}
}
}
/**
* 处理标准装饰器
* @param cache
* @return
*/
private Cache setStandardDecorators(Cache cache) {
try {
MetaObject metaCache = SystemMetaObject.forObject(cache);
if (size != null && metaCache.hasSetter("size")) {
metaCache.setValue("size", size);
}
if (clearInterval != null) {
// 定其清理
cache = new ScheduledCache(cache);
((ScheduledCache) cache).setClearInterval(clearInterval);
}
if (readWrite) {
// 序列化
cache = new SerializedCache(cache);
}
// 日志
cache = new LoggingCache(cache);
// 同步
cache = new SynchronizedCache(cache);
// 阻塞
if (blocking) {
cache = new BlockingCache(cache);
}
return cache;
} catch (Exception e) {
throw new CacheException(...);
}
}
复制代码
默认情况下,缓存的实现类为PerpetualCache
,对于默认的缓存,实例化后,会根据配置的参数进行相关的装饰操作,如配置了clearInterval
参数,就会使用ScheduledCache
进行装饰:
@Override
public void putObject(Object key, Object object) {
clearWhenStale();
delegate.putObject(key, object);
}
@Override
public Object getObject(Object key) {
return clearWhenStale() ? null : delegate.getObject(key);
}
private boolean clearWhenStale() {
// 判断是否超时
if (System.currentTimeMillis() - lastClear > clearInterval) {
clear();
return true;
}
return false;
}
复制代码
ScheduledCache
的功能是定期清理缓存,在put
、get
操作时,都会判断是否超时,如果超时了就会触发清理操作。
mybatis
的cache
实现及装饰器如下:
可以看到,真正的实现只有PerpetualCache
,其余的均为装饰器。
来看看得到的Cache
:
由于层层装饰,cache
的层次有点多,最底层的cache
为PerpetualCache
,这是真正干活的类,它的id
为org.apache.ibatis.demo.mapper.UserMapper
,也就是Mapper
接口的包名.类名
。
我们继续看看configuration.addCache(cache)
操作:
protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
public void addCache(Cache cache) {
caches.put(cache.getId(), cache);
}
复制代码
这一步的操作就是把cache
保存到caches
中了。注意到caches
类型为StrictMap
,稍微看下它的定义:
protected static class StrictMap<V> extends HashMap<String, V> {
...
}
复制代码
就是HashMap
的子类了。
这一步得到的caches
的结果如下:
这一步得到cache
后,在后面解析select|insert|update|delete
语句时,会把得到的当前得到的cache
一并放入到MappedStatement
对象中,相关操作为MapperBuilderAssistant#addMappedStatement(...)
方法,就不过多分析了。
2.3 TransactionalCacheManager
再回到CachingExecutor#query(...)
方法,处理操作的方法如下:
public class CachingExecutor implements Executor {
/**
* 缓存管理
*/
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 {
// 这个就是 PerpetualCache 装饰后的对象
Cache cache = ms.getCache();
// 操作缓存
if (cache != null) {
// 从缓存中获取
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
...
// 添加到缓存中
tcm.putObject(cache, key, list);
}
...
}
...
}
...
}
复制代码
可以看到,得到cache
后,使用TransactionalCacheManager
进行操作,我们进入TransactionalCacheManager
看看相关方法:
public class TransactionalCacheManager {
// 也是一个Map,保存的是 TransactionalCache
private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
public Object getObject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getObject(key);
}
/**
* 从transactionalCaches 中获取 TransactionalCache,如果不存在则新建
*/
private TransactionalCache getTransactionalCache(Cache cache) {
return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
}
...
}
复制代码
关于TransactionalCache
,它也是Cache
的实现类:
public class TransactionalCache implements Cache {
/**
* 真正的缓存操作类
*/
private final Cache delegate;
private final Set<Object> entriesMissedInCache;
...
public TransactionalCache(Cache delegate) {
this.delegate = delegate;
this.entriesToAddOnCommit = new HashMap<>();
...
}
@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 putObject(Object key, Object object) {
// 先添加到临时缓存中
entriesToAddOnCommit.put(key, object);
}
/**
* 处理事务的提交操作
* 事务提交时,才把临时缓存中的缓存添加到缓存中
*/
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);
}
}
}
...
}
复制代码
这个类实现了如下功能:
- 它也是个包装类,实现了缓存的事务功能
- 添加对象时,会行添加到临时缓存中,当事务提交后,才会真正添加到二级缓存中
- 对象的移除也是类似的操作,只有当事务提交后才会真正操作二级缓存
2.4 使用自定义二级缓存
从上面的分析来看,mybatis
为每个mapper.xml
(namespace
)都创建了一个缓存对象(默认实现类为PerpetualCache
),与MappedStatement
绑定在一起,因此二级缓存的作用范围为全局,而且对于每个namespace
都对应一个缓存对象。
二级缓存的默认实现为PerpetualCache
,我们来看看它的内容:
public class PerpetualCache implements Cache {
...
// 用hashmap来保存记录
private final Map<Object, Object> cache = new HashMap<>();
@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();
}
...
}
复制代码
从代码来看,PerpetualCache
其中维护了一个名为cache
的成员变量,它是HashMap
类型的,缓存的添加
/获取
/清除
等都是操作这个HashMap
,mybatis
的二级缓存同样也是保存在内存中的!
HashMap
并不是线程安全的,我们在多线程环境下能使用二级缓存的默认实现(PerpetualCache
)吗?答案是可以,虽然HashMap
不是线程安全的,但经过缓存装饰器之后,二级缓存的添加
/获取
/清除
等操作就是线程安全了,这就是SynchronizedCache
的功劳:
public class SynchronizedCache implements Cache {
// 被装饰的对象,也是真正干活的对象
private final Cache delegate;
...
public SynchronizedCache(Cache delegate) {
this.delegate = delegate;
}
@Override
public synchronized void putObject(Object key, Object object) {
delegate.putObject(key, object);
}
@Override
public synchronized Object getObject(Object key) {
return delegate.getObject(key);
}
@Override
public synchronized Object removeObject(Object key) {
return delegate.removeObject(key);
}
...
}
复制代码
只是简单地在添加
/获取
/清除
等操作方法上加了synchronized
关键字,至于性能方面就呵呵了。
为了更好地发挥二级缓存的功能,mybatis
可以让开发者自定义二级缓存的实现:
注意,如果使用了自定义缓存,那么eviction
,flushInterval
,size
,readOnly
等配置将不会生效,这点在CacheBuilder#build
方法中也能得到体现:
在自定义缓存时,我们可以使用redis等分布式缓存来存储数据,这里就不多分析了。
2.5 何时使用二级缓存
分析完了二级缓存,什么情况下适合使用二级缓存(mybits
的默认实现)呢?
从前面的分析来看,二级缓存的默认实现有以下特点:
- 作用范围为全局(整个jvm进程)
- 保存在内存中(不支持分布式)
- 并发性能低(操作方法添加了
synchronized
关键字)
结合以上所述,mybatis
提供的二级缓存只 适合在单机、对并发要求不高的情况下使用。
需要注意的是,以上条件是针对mybats
提供的二级缓存默认实现,我们还可以自定义二级缓存,以实现其在分布式、高并发环境的使用条件。
3. 一二级缓存特性汇总
3.1 缓存的配置汇总
cacheEnabled
在 mybatis 配置文件中,有一个settings
节点,下面有一配置为cacheEnabled
:
这个cacheEnabled
开启或关闭的是哪个缓存呢?
让我们再回到Configuration#newExecutor(...)
方法:
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
...
// 缓存装饰
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
...
}
复制代码
这个cacheEnabled
就是settings
节点下cacheEnabled
的值。从代码来看,当cacheEnabled
为false
时,CachingExecutor
就不会创建了,而 CachingExecutor
是用来处理二级缓存的,这样一来,二缓存就不能使用了,即使在mapper.xml
中加了<cache/>
标签,也不能使用二级缓存了。
那一级缓存还能使用吗?从前面的分析来看,二级缓存的操作是在BaseExecutor
中,因此并不会受这里的影响。
3.2 mybatis缓存对比
缓存类型 | 作用范围 | 保存位置 | 是否线程安全 | 性能 | 是否支持分布式 |
---|---|---|---|---|---|
一级 | session | 内存 | 否 | 高 | 否 |
二级(默认实现) | 全局 | 内存 | 是 | 低 | 否 |
3.3 合理使用mybatis缓存
如何合理使用mybatis
缓存呢?本人给出的建议是:
- 对于一级缓存,
localCacheScope
设置为STATEMENT
; - 对于二级缓存,如果使用
mybatis
默认提供的,在单机、并发要求不高的情况下可以使用,多机情况下千万不要使用;如果使用自定义缓存,可以根据使用场景自由进行缓存实现。
本文原文链接:my.oschina.net/funcy/blog/… ,限于作者个人水平,文中难免有错误之处,欢迎指正!原创不易,商业转载请联系作者获得授权,非商业转载请注明出处。