MyBatis深入理解和使用-MyBatis缓存体系
一级缓存(session级别)
为什么MyBatis默认使用一级缓存
一级缓存工作流程
结果存在,则直接将缓存结果返回;
结果不存在:
去数据库中查询数据,得到查询结果;
将key和查询到的结果分别作为key,value对存储到Cache中;
将查询结果返回;
一级缓存的实现
// SqlSession 由 SqlSessionFactoryBuilder创建,解析xml或者是使用java配置等;
public interface SqlSession extends Closeable {
...
/**
* Execute an insert statement.
*/
int insert(String statement);
/**
* Execute an insert statement with the given parameter object. Any generated
* autoincrement values or selectKey entries will modify the given parameter
* object properties. Only the number of rows affected will be returned。
*/
int insert(String statement, Object parameter);
/**
* Execute an update statement. The number of rows affected will be returned.
*/
int update(String statement);
/**
* Execute an update statement. The number of rows affected will be returned.
* @param statement Unique identifier matching the statement to execute.
* @param parameter A parameter object to pass to the statement.
* @return int The number of rows affected by the update.
*/
int update(String statement, Object parameter);
/**
* Execute a delete statement. The number of rows affected will be returned.
*/
int delete(String statement);
/**
* Execute a delete statement. The number of rows affected will be returned.
*/
int delete(String statement, Object parameter);
/**
* Closes the session
*/
@Override
void close();
/**
* Clears local session cache
*/
void clearCache();
...
}
// CacheKey key = statementId + rowBounds + SQL + 参数值
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;
}
// 查询SQL
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 (this.closed) {
throw new ExecutorException("Executor was closed.");
} else {
if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
this.clearLocalCache();
}
List list;
try {
++this.queryStack;
// localCache.getObject(key) 对应(PerpetualCache中)
// private Map<Object, Object> cache = new HashMap<Object, Object>();
list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
if (list != null) {
// 处理缓存
this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 执行数据库查询queryFromDatabase
list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
--this.queryStack;
}
if (this.queryStack == 0) {
Iterator var8 = this.deferredLoads.iterator();
while(var8.hasNext()) {
BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)var8.next();
deferredLoad.load();
}
this.deferredLoads.clear();
if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
this.clearLocalCache();
}
}
return list;
}
}
// 数据库查询语句
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);
List list;
try {
list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
this.localCache.removeObject(key);
}
this.localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
this.localOutputParameterCache.putObject(key, parameter);
}
return list;
}
/**
* PerpetualCache 用来维护一级缓存
*/
public class PerpetualCache implements Cache {
private String id;
private Map<Object, Object> cache = new HashMap<Object, Object>();
public PerpetualCache(String id) {
this.id = id;
}
@Override
public String getId() {
return id;
}
@Override
public int getSize() {
return cache.size();
}
@Override
public void putObject(Object key, Object value) {
cache.put(key, value);
}
//通过cacheKey判断是否走缓存
@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 ReadWriteLock getReadWriteLock() {
return null;
}
@Override
public boolean equals(Object o) {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
if (this == o) {
return true;
}
if (!(o instanceof Cache)) {
return false;
}
Cache otherCache = (Cache) o;
return getId().equals(otherCache.getId());
}
@Override
public int hashCode() {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
return getId().hashCode();
}
}
Cache cache = ms.getCache();
public class CachingExecutor implements Executor {
private GpConfiguration configuration;
private SimpleExecutor delegate;
private Map<String,Object> localCache = new HashMap();
public CachingExecutor(SimpleExecutor delegate) {
this.delegate = delegate;
}
public CachingExecutor(GpConfiguration configuration) {
this.configuration = configuration;
}
public <E> E query(MapperRegistory.MapperData mapperData, Object parameter)
throws Exception {
//初始化StatementHandler --> ParameterHandler --> ResultSetHandler
StatementHandler handler = new StatementHandler(configuration);
Object result = localCache.get(mapperData.getSql());
if( null != result){
System.out.println("缓存命中");
return (E)result;
}
result = (E) delegate.query(mapperData,parameter);
localCache.put(mapperData.getSql(),result);
return (E)result;
}
}
二级缓存(Mapper级别)
二级缓存开启的标志
cacheEnabled | 全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存。 | true | false | true |
核心类
public interface Cache {
//获取Id
String getId();
//根据key添加数据缓存信息
void putObject(Object key, Object value);
//根据key获取数据缓存信息
Object getObject(Object key);
//根据key删除数据缓存信息
Object removeObject(Object key);
//清空缓存
void clear();
//计算大小
int getSize();
//读写锁的获取
ReadWriteLock getReadWriteLock();
}
二级缓存的默认实现
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;
}
@Override
public String getId() {
return id;
}
@Override
public int getSize() {
return cache.size();
}
@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 ReadWriteLock getReadWriteLock() {
return null;
}
@Override
public boolean equals(Object o) {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
if (this == o) {
return true;
}
if (!(o instanceof Cache)) {
return false;
}
Cache otherCache = (Cache) o;
return getId().equals(otherCache.getId());
}
@Override
public int hashCode() {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
return getId().hashCode();
}
}
除了默认实现之外,MyBatis还提供如下几种缓存实现算法:
LRU:最近最少使用算法,当缓存中容量满了,会将缓存中最近做少被使用的缓存记录清除掉,然后添加新的记录。
具体实现:org.apache.ibatis.cache.decorators.LruCache
FIFO:先进先出算法,当缓存中容量满了,会将最先进入缓存中的数据清除掉,然后添加新的记录。
具体实现:org.apache.ibatis.cache.decorators.FifoCache
Scheduled:指定时间间隔清空算法,该算法会以指定的某一个时间间隔将Cache缓存中的数据清空。
具体实现:org.apache.ibatis.cache.decorators.ScheduledCache
在实际的生产中特别是分布式架构的项目,一般不会采用MyBatis数据持久层作为二级缓存,一般采用Redis或者MongoDB等分布式缓存中间件代替,因此,在使用MyBatis二级缓存时要明确场景。
MyBatis深入理解和使用-TypeHandler:https://blog.csdn.net/shang_xs/article/details/86656173
MyBatis深入理解和使用-MyBatis事务管理:https://blog.csdn.net/shang_xs/article/details/86656649