什么是一级缓存?
commit/close/clear
时,则进行清空缓存。一级缓存如何设计?
https://article-images.zsxq.com/Fq6qNr4inPGf3qav-TzvEntASm4n
SESSION
级别,也就是使用一级缓存保存会话生命周期内的数据。
insert/delete/update
则清空缓存。close、commit、clear
操作时,也要顺便把缓存数据清空。这样才能尽最大可能的提高查询效率的同时,降低发生脏读的可能。mybatis-step-17
|-src
|-main
| |-java
| |-com.lino.mybatis
| |-annotations
| | |-Delete.java
| | |-Insert.java
| | |-Select.java
| | |-Update.java
| |-binding
| | |-MapperMethod.java
| | |-MapperProxy.java
| | |-MapperProxyFactory.java
| | |-MapperRegistry.java
| |-builder
| | |-annotations
| | | |-MapperAnnotationBuilder.java
| | |-xml
| | | |-XMLConfigBuilder.java
| | | |-XMLMapperBuilder.java
| | | |-XMLStatementBuilder.java
| | |-BaseBuilder.java
| | |-MapperBuilderAssistant.java
| | |-ParameterExpression.java
| | |-ResultMapResolver.java
| | |-SqlSourceBuilder.java
| | |-StaticSqlSource.java
| |-cache
| | |-impl
| | | |-PrepetualCache.java
| | |-Cache.java
| | |-CacheKey.java
| | |-NullCacheKey.java
| |-datasource
| | |-druid
| | | |-DruidDataSourceFacroty.java
| | |-pooled
| | | |-PooledConnection.java
| | | |-PooledDataSource.java
| | | |-PooledDataSourceFacroty.java
| | | |-PoolState.java
| | |-unpooled
| | | |-UnpooledDataSource.java
| | | |-UnpooledDataSourceFacroty.java
| | |-DataSourceFactory.java
| |-executor
| | |-keygen
| | | |-Jdbc3KeyGenerator.java
| | | |-KeyGenerator.java
| | | |-NoKeyGenerator.java
| | | |-SelectKeyGenerator.java
| | |-parameter
| | | |-ParameterHandler.java
| | |-result
| | | |-DefaultResultContext.java
| | | |-DefaultResultHandler.java
| | |-resultset
| | | |-DefaultResultSetHandler.java
| | | |-ResultSetHandler.java
| | | |-ResultSetWrapper.java
| | |-statement
| | | |-BaseStatementHandler.java
| | | |-PreparedStatementHandler.java
| | | |-SimpleStatementHandler.java
| | | |-StatementHandler.java
| | |-BaseExecutor.java
| | |-ExecutionPlaceholder.java
| | |-Executor.java
| | |-SimpleExecutor.java
| |-io
| | |-Resources.java
| |-mapping
| | |-BoundSql.java
| | |-Environment.java
| | |-MappedStatement.java
| | |-ParameterMapping.java
| | |-ResultFlag.java
| | |-ResultMap.java
| | |-ResultMapping.java
| | |-SqlCommandType.java
| | |-SqlSource.java
| |-parsing
| | |-GenericTokenParser.java
| | |-TokenHandler.java
| |-plugin
| | |-Interceptor.java
| | |-InterceptorChain.java
| | |-Intercepts.java
| | |-Invocation.java
| | |-Plugin.java
| | |-Signature.java
| |-reflection
| | |-factory
| | | |-DefaultObjectFactory.java
| | | |-ObjectFactory.java
| | |-invoker
| | | |-GetFieldInvoker.java
| | | |-Invoker.java
| | | |-MethodInvoker.java
| | | |-SetFieldInvoker.java
| | |-property
| | | |-PropertyNamer.java
| | | |-PropertyTokenizer.java
| | |-wrapper
| | | |-BaseWrapper.java
| | | |-BeanWrapper.java
| | | |-CollectionWrapper.java
| | | |-DefaultObjectWrapperFactory.java
| | | |-MapWrapper.java
| | | |-ObjectWrapper.java
| | | |-ObjectWrapperFactory.java
| | |-MetaClass.java
| | |-MetaObject.java
| | |-Reflector.java
| | |-SystemMetaObject.java
| |-scripting
| | |-defaults
| | | |-DefaultParameterHandler.java
| | | |-RawSqlSource.java
| | |-xmltags
| | | |-DynamicContext.java
| | | |-DynamicSqlSource.java
| | | |-ExpressionEvaluator.java
| | | |-IfSqlNode.java
| | | |-MixedSqlNode.java
| | | |-OgnlCache.java
| | | |-OgnlClassResolver.java
| | | |-SqlNode.java
| | | |-StaticTextSqlNode.java
| | | |-TextSqlNode.java
| | | |-TrimSqlNode.java
| | | |-XMLLanguageDriver.java
| | | |-XMLScriptBuilder.java
| | |-LanguageDriver.java
| | |-LanguageDriverRegistry.java
| |-session
| | |-defaults
| | | |-DefaultSqlSession.java
| | | |-DefaultSqlSessionFactory.java
| | |-Configuration.java
| | |-LocalCacheScope.java
| | |-ResultContext.java
| | |-ResultHandler.java
| | |-RowBounds.java
| | |-SqlSession.java
| | |-SqlSessionFactory.java
| | |-SqlSessionFactoryBuilder.java
| | |-TransactionIsolationLevel.java
| |-transaction
| | |-jdbc
| | | |-JdbcTransaction.java
| | | |-JdbcTransactionFactory.java
| | |-Transaction.java
| | |-TransactionFactory.java
| |-type
| | |-BaseTypeHandler.java
| | |-DateTypeHandler.java
| | |-IntegerTypeHandler.java
| | |-JdbcType.java
| | |-LongTypeHandler.java
| | |-SimpleTypeRegistry.java
| | |-StringTypeHandler.java
| | |-TypeAliasRegistry.java
| | |-TypeHandler.java
| | |-TypeHandlerRegistry.java
|-test
|-java
| |-com.lino.mybatis.test
| |-dao
| | |-IActivityDao.java
| |-plugin
| | |-TestPlugin.java
| |-po
| | |-Activity.java
| |-ApiTest.java
|-resources
|-mapper
| |-Activity_Mapper.xml
|-mybatis-config-datasource.xml
LocalCacheScope
配置的方式进行处理。insert/update/delete
以及关闭、提交、清空等操作时,在缓存组件中删除相关的缓存数据。Key-Value
方式存储,Mybatis 对于其 Key 的生成采取规则为:[mappedStatementId + offset + limit + SQL + queryParams + environment]
生成一个哈希码作为 Key 使用。cache
包,用于处理数据的缓存操作,一级缓存、二级缓存的操作都是这个包下提供的服务。Cache.java
package com.lino.mybatis.cache;
/**
* @description: 缓存接口
*/
public interface Cache {
/**
* 获取ID,每个缓存都有唯一的ID标识
*
* @return ID
*/
String getId();
/**
* 存入值
*
* @param key 键
* @param value 值
*/
void putObject(Object key, Object value);
/**
* 获取值
*
* @param key 键
* @return 值
*/
Object getObject(Object key);
/**
* 删除值
*
* @param key 键
* @return 值
*/
Object removeObject(Object key);
/**
* 清空
*/
void clear();
/**
* 获取缓存大小
*
* @return 缓存大小
*/
int getSize();
}
PerpetualCache.java
package com.lino.mybatis.cache.impl;
import com.alibaba.fastjson.JSON;
import com.lino.mybatis.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
/**
* @description: 一级缓存,在 Session 生命周期内一直保持,每创建新的 OpenSession 都会创建一个缓存器 PerpetualCache
*/
public class PerpetualCache implements Cache {
private Logger logger = LoggerFactory.getLogger(PerpetualCache.class);
private String id;
/**
* 使用HashMap存放一级缓存数据,session 生命周期较短,正常情况下数据不会一直在缓存存放
*/
private Map<Object, Object> cache = new HashMap<>(16);
public PerpetualCache(String id) {
this.id = id;
}
@Override
public String getId() {
return id;
}
@Override
public void putObject(Object key, Object value) {
cache.put(key, value);
}
@Override
public Object getObject(Object key) {
Object obj = cache.get(key);
if (null != obj) {
logger.info("一级缓存 \r\nkey:{} \r\nval:{}", key, JSON.toJSONString(obj));
}
return obj;
}
@Override
public Object removeObject(Object key) {
return cache.remove(key);
}
@Override
public void clear() {
cache.clear();
}
@Override
public int getSize() {
return cache.size();
}
}
CacheKey.java
package com.lino.mybatis.cache;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
/**
* @description: 缓存key,一般缓存框架的数据结构基本上都是 key-value 方式存储
* Mybatis 对于其 key 的生成采取规则为:[mappedStatementId + offset + limit + SQL + queryParams + environment]生成一个哈希码
*/
public class CacheKey implements Cloneable, Serializable {
private static final long serialVersionUID = 1146682552656046210L;
public static final CacheKey NULL_CACHE_KEY = new NullCacheKey();
private static final int DEFAULT_MULTIPLYEP = 37;
private static final int DEFAULT_HASHCODE = 17;
private int multiplier;
private int hashcode;
private long checksum;
private int count;
private List<Object> updateList;
public CacheKey() {
this.hashcode = DEFAULT_HASHCODE;
this.multiplier = DEFAULT_MULTIPLYEP;
this.count = 0;
this.updateList = new ArrayList<>();
}
public CacheKey(Object[] objects) {
this();
updateAll(objects);
}
public int getUpdateCount() {
return updateList.size();
}
public void update(Object object) {
if (object != null && object.getClass().isArray()) {
int length = Array.getLength(object);
for (int i = 0; i < length; i++) {
Object element = Array.get(object, i);
doUpdate(element);
}
} else {
doUpdate(object);
}
}
private void doUpdate(Object object) {
// 计算hash值,校验码
int baseHashCode = object == null ? 1 : object.hashCode();
count++;
checksum += baseHashCode;
baseHashCode *= count;
hashcode = multiplier + hashcode + baseHashCode;
updateList.add(object);
}
public void updateAll(Object[] objects) {
for (Object o : objects) {
update(o);
}
}
@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 (thisObject == null) {
if (thatObject != null) {
return false;
}
} else {
if (!thisObject.equals(thatObject)) {
return false;
}
}
}
return true;
}
@Override
public int hashCode() {
return hashcode;
}
@Override
public String toString() {
StringBuilder returnValue = new StringBuilder().append(hashcode).append(":").append(checksum);
for (Object obj : updateList) {
returnValue.append(":").append(obj);
}
return returnValue.toString();
}
@Override
public CacheKey clone() throws CloneNotSupportedException {
CacheKey clonedCacheKey = (CacheKey) super.clone();
clonedCacheKey.updateList = new ArrayList<>(updateList);
return clonedCacheKey;
}
}
doUpdate
:哈希计算
[mappedStatementId + offset + limit + SQL + queryParams + environment]
生成一个哈希码。doUpdate
方法的 object
入参对象就是用于拼装哈希值的具体操作。equals
:哈希 equal
equals
对比方法。doUpdate
计算哈希方法时,把对象添加到 updateList.add(object)
集合中,就是用于这里的 equals
判断使用NullCacheKey.java
package com.lino.mybatis.cache;
/**
* @description: NULL值缓存key
*/
public class NullCacheKey extends CacheKey {
private static final long serialVersionUID = 3704229911977019465L;
public NullCacheKey() {
super();
}
}
<settings>
<setting name="localCacheScope" value="SESSION"/>
settings>
localCacheScope
缓存机制的属性值有两个,SESSION、STATEMENT
。
STATEMENT
:关闭一级缓存。LocalCacheScope.java
package com.lino.mybatis.session;
/**
* @description: 本地缓存机制
*/
public enum LocalCacheScope {
/**
* 本地缓存
*/
SESSION, STATEMENT
}
SESSION, STATEMENT
SESSION
:为默认值,支持使用一级缓存。STATEMENT
:不支持使用一级缓存。ExecutionPlaceholder.java
package com.lino.mybatis.executor;
/**
* @description: 占位符
*/
public enum ExecutionPlaceholder {
/**
* 占位符
*/
EXECUTION_PLACEHOLDER
}
MappedStatement.java
package com.lino.mybatis.mapping;
import com.lino.mybatis.executor.keygen.Jdbc3KeyGenerator;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.executor.keygen.NoKeyGenerator;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import java.util.Collections;
import java.util.List;
/**
* @description: 映射器语句类
*/
public class MappedStatement {
private String resource;
private Configuration configuration;
private String id;
private SqlCommandType sqlCommandType;
private SqlSource sqlSource;
Class<?> resultType;
private LanguageDriver lang;
private List<ResultMap> resultMaps;
private boolean flushCacheRequired;
...
public MappedStatement() {
}
...
public boolean isFlushCacheRequired() {
return flushCacheRequired;
}
}
Configuration.java
package com.lino.mybatis.session;
import com.lino.mybatis.binding.MapperRegistry;
import com.lino.mybatis.datasource.druid.DruidDataSourceFactory;
import com.lino.mybatis.datasource.pooled.PooledDataSourceFactory;
import com.lino.mybatis.datasource.unpooled.UnpooledDataSourceFactory;
import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.executor.SimpleExecutor;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.executor.parameter.ParameterHandler;
import com.lino.mybatis.executor.resultset.DefaultResultSetHandler;
import com.lino.mybatis.executor.resultset.ResultSetHandler;
import com.lino.mybatis.executor.statement.PreparedStatementHandler;
import com.lino.mybatis.executor.statement.StatementHandler;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.Environment;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.ResultMap;
import com.lino.mybatis.plugin.Interceptor;
import com.lino.mybatis.plugin.InterceptorChain;
import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.reflection.factory.DefaultObjectFactory;
import com.lino.mybatis.reflection.factory.ObjectFactory;
import com.lino.mybatis.reflection.wrapper.DefaultObjectWrapperFactory;
import com.lino.mybatis.reflection.wrapper.ObjectWrapperFactory;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.scripting.LanguageDriverRegistry;
import com.lino.mybatis.scripting.xmltags.XMLLanguageDriver;
import com.lino.mybatis.transaction.Transaction;
import com.lino.mybatis.transaction.jdbc.JdbcTransactionFactory;
import com.lino.mybatis.type.TypeAliasRegistry;
import com.lino.mybatis.type.TypeHandlerRegistry;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* @description: 配置项
*/
public class Configuration {
/**
* 环境
*/
protected Environment environment;
/**
* 是否使用自动生成键值对
*/
protected boolean useGeneratedKeys = false;
/**
* 缓存机制,默认不配置的情况是 SESSION
*/
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
...
public LocalCacheScope getLocalCacheScope() {
return localCacheScope;
}
public void setLocalCacheScope(LocalCacheScope localCacheScope) {
this.localCacheScope = localCacheScope;
}
...
}
LocalCacheScope
缓存机制。new PerpetualCache("LocalCache")
一级缓存,并在执行器中完成缓存存放、使用、删除等操作。Executor.java
package com.lino.mybatis.executor;
import com.lino.mybatis.cache.CacheKey;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.transaction.Transaction;
import java.sql.SQLException;
import java.util.List;
/**
* @description: 执行器
*/
public interface Executor {
/**
* 结果处理器
*/
ResultHandler NO_RESULT_HANDLER = null;
/**
* 更新
*
* @param ms 映射器语句
* @param parameter 参数
* @return 返回的是受影响的行数
* @throws SQLException SQL异常
*/
int update(MappedStatement ms, Object parameter) throws SQLException;
/**
* 查询,含缓存
*
* @param ms 映射器语句
* @param parameter 参数
* @param rowBounds 分页记录限制
* @param resultHandler 结果处理器
* @param key 缓存key
* @param boundSql SQL对象
* @param 返回的类型
* @return List
* @throws SQLException SQL异常
*/
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException;
/**
* 查询
*
* @param ms 映射器语句
* @param parameter 参数
* @param rowBounds 分页记录限制
* @param resultHandler 结果处理器
* @param 返回的类型
* @return List
* @throws SQLException SQL异常
*/
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
/**
* 获取事务
*
* @return 事务对象
*/
Transaction getTransaction();
/**
* 提交
*
* @param required 是否请求执行
* @throws SQLException SQL异常
*/
void commit(boolean required) throws SQLException;
/**
* 回滚
*
* @param required 是否请求执行
* @throws SQLException SQL异常
*/
void rollback(boolean required) throws SQLException;
/**
* 关闭
*
* @param forceRollback 是否强制回滚
*/
void close(boolean forceRollback);
/**
* 清理session缓存
*/
void clearLocalCache();
/**
* 创建缓存key
*
* @param ms 映射器语句
* @param parameterObject 参数对象
* @param rowBounds 分页记录限制
* @param boundSql SQL对象
* @return 缓存key
*/
CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
}
query
方法,会被另外一个不含有缓存 Key 的 query
方法调用。BaseExecutor
package com.lino.mybatis.executor;
import com.lino.mybatis.cache.CacheKey;
import com.lino.mybatis.cache.impl.PerpetualCache;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.ParameterMapping;
import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.LocalCacheScope;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.transaction.Transaction;
import com.lino.mybatis.type.TypeHandlerRegistry;
import org.slf4j.LoggerFactory;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
/**
* @description: 执行器抽象基类
*/
public abstract class BaseExecutor implements Executor {
private org.slf4j.Logger logger = LoggerFactory.getLogger(BaseExecutor.class);
protected Configuration configuration;
protected Transaction transaction;
protected Executor wrapper;
/**
* 本地缓存
*/
protected PerpetualCache localCache;
private boolean closed;
/**
* 查询堆栈
*/
protected int queryStack = 0;
public BaseExecutor(Configuration configuration, Transaction transaction) {
this.configuration = configuration;
this.transaction = transaction;
this.wrapper = this;
this.localCache = new PerpetualCache("LocalCache");
}
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
if (closed) {
throw new RuntimeException("Executor was closed.");
}
clearLocalCache();
return doUpdate(ms, parameter);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
if (closed) {
throw new RuntimeException("Executor was closed.");
}
// 清理局部缓存,查询堆栈为0则清理。queryStack 避免递归调用清理
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
// 根据cacheKey从localCache中查询数据
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list == null) {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
if (configuration.getLocalCacheScope() == LocalCacheScope.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, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
// 存入缓存
localCache.putObject(key, list);
return list;
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 1.获取绑定SQL
BoundSql boundSql = ms.getBoundSql(parameter);
// 2.创建缓存key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
/**
* 更新方法
*
* @param ms 映射器语句
* @param parameter 参数
* @return 返回的是受影响的行数
* @throws SQLException SQL异常
*/
protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;
/**
* 查询方法
*
* @param ms 映射器语句
* @param parameter 参数
* @param rowBounds 分页记录限制
* @param resultHandler 结果处理器
* @param boundSql SQL对象
* @param 返回的类型
* @return List
* @throws SQLException SQL异常
*/
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException;
@Override
public Transaction getTransaction() {
if (closed) {
throw new RuntimeException("Executor was closed.");
}
return transaction;
}
@Override
public void commit(boolean required) throws SQLException {
if (closed) {
throw new RuntimeException("Cannot commit, transaction is already closed.");
}
clearLocalCache();
if (required) {
transaction.commit();
}
}
@Override
public void rollback(boolean required) throws SQLException {
if (!closed) {
try {
clearLocalCache();
} finally {
if (required) {
transaction.rollback();
}
}
}
}
@Override
public void clearLocalCache() {
if (!closed) {
localCache.clear();
}
}
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new RuntimeException("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();
for (ParameterMapping parameterMapping : parameterMappings) {
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) {
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
@Override
public void close(boolean forceRollback) {
try {
try {
rollback(forceRollback);
} finally {
transaction.close();
}
} catch (SQLException e) {
logger.warn("Unexpected exception on closing transaction. Cause: " + e);
} finally {
transaction = null;
closed = true;
}
}
/**
* 关闭语句
*
* @param statement 语句
*/
protected void closeStatement(Statement statement) {
if (statement != null) {
try {
statement.close();
} catch (SQLException ignore) {
}
}
}
}
query
:查询,不含缓存方法
BaseExecutor#query
主要新增加了关于缓存 Key 的创建,创建后调用重载的另外一个含有缓存 Key 的 query
方法。创建缓存KEY
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new RuntimeException("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();
for (ParameterMapping parameterMapping : parameterMappings) {
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) {
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
[mappedStatementId + offset + limit + SQL + queryParams + environment]
信息构建出一个哈希值。cacheKey#update
方法。查询数据缓存
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
if (closed) {
throw new RuntimeException("Executor was closed.");
}
// 清理局部缓存,查询堆栈为0则清理。queryStack 避免递归调用清理
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
// 根据cacheKey从localCache中查询数据
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list == null) {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache();
}
}
return list;
}
query
查询操作中,判断 queryStack
是否为0且是否刷新请求,如果是则会操作清空缓存。queryStack
自增以后通过 localCache
获取缓存数据。
queryFromDatabase
方法从数据库查询数据并返回结果。LocalCacheScope
会在这里判断,如果不是 SESSION
机制,则清空缓存。存放缓存数据
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, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
// 存入缓存
localCache.putObject(key, list);
return list;
}
query
查询的时候,会把数据库查询到的数据,使用 localCache.putObject(key, list)
存放缓存中。删除缓存数据
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
if (closed) {
throw new RuntimeException("Executor was closed.");
}
clearLocalCache();
return doUpdate(ms, parameter);
}
@Override
public void commit(boolean required) throws SQLException {
if (closed) {
throw new RuntimeException("Cannot commit, transaction is already closed.");
}
clearLocalCache();
if (required) {
transaction.commit();
}
}
@Override
public void rollback(boolean required) throws SQLException {
if (!closed) {
try {
clearLocalCache();
} finally {
if (required) {
transaction.rollback();
}
}
}
}
@Override
public void clearLocalCache() {
if (!closed) {
localCache.clear();
}
}
@Override
public void close(boolean forceRollback) {
try {
try {
rollback(forceRollback);
} finally {
transaction.close();
}
} catch (SQLException e) {
logger.warn("Unexpected exception on closing transaction. Cause: " + e);
} finally {
transaction = null;
closed = true;
}
}
insert、delete、update
,都是调用执行器的 update
方法进行处理的。update、commit、rollback、close
,都会调用到 clearLocalCache
执行缓存清空。clearLocalCache
也是对外的,所以你也可以在缓存机制为 SESSION
级别下,手动清空缓存操作。XMLConfigBuilder.java
package com.lino.mybatis.builder.xml;
import com.lino.mybatis.builder.BaseBuilder;
import com.lino.mybatis.datasource.DataSourceFactory;
import com.lino.mybatis.io.Resources;
import com.lino.mybatis.mapping.Environment;
import com.lino.mybatis.plugin.Interceptor;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.LocalCacheScope;
import com.lino.mybatis.transaction.TransactionFactory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.xml.sax.InputSource;
import javax.sql.DataSource;
import java.io.InputStream;
import java.io.Reader;
import java.util.List;
import java.util.Properties;
/**
* @description: XML配置构建器,建造者模式,集成BaseBuilder
*/
public class XMLConfigBuilder extends BaseBuilder {
...
/**
* 解析配置:类型别名、插件、对象工厂、对象包装工厂、设置、环境、类型转换、映射器
*
* @return Configuration
*/
public Configuration parse() {
try {
// 插件添加
pluginElement(root.element("plugins"));
// 设置
settingElement(root.element("settings"));
// 环境
environmentsElement(root.element("environments"));
// 解析映射器
mapperElement(root.element("mappers"));
} catch (Exception e) {
throw new RuntimeException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
return configuration;
}
...
/**
*
*
*
*
*/
private void settingElement(Element context) {
if (context == null) {
return;
}
List<Element> elements = context.elements();
Properties props = new Properties();
for (Element element : elements) {
props.setProperty(element.attributeValue("name"), element.attributeValue("value"));
}
configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope")));
}
...
}
Configuration
配置项中。mybatis-config-datasource.xml
DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<plugins>
<plugin interceptor="com.lino.mybatis.test.plugin.TestPlugin">
<property name="test00" value="100"/>
<property name="test01" value="200"/>
plugin>
plugins>
<settings>
<setting name="localCacheScope" value="SESSION"/>
settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="mapper/Activity_Mapper.xml"/>
mappers>
configuration>
ApiTest.java
@Test
public void test_queryActivityById() {
// 1.获取映射器对象
IActivityDao dao = sqlSession.getMapper(IActivityDao.class);
// 2.测试验证
Activity activity = new Activity();
activity.setActivityId(100001L);
logger.info("测试结果:{}", JSON.toJSONString(dao.queryActivityById(activity)));
logger.info("测试结果:{}", JSON.toJSONString(dao.queryActivityById(activity)));
}
session
后执行力两次相同的查询。验证缓存的使用测试结果
14:16:03.602 [main] INFO c.l.m.d.pooled.PooledDataSource - Created connention 1610525991.
拦截SQL:SELECT activity_id, activity_name, activity_desc, create_time, update_time
FROM activity
where activity_id = ?
14:16:03.617 [main] INFO c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:100001
14:16:03.617 [main] INFO com.lino.mybatis.test.ApiTest - 测试结果:{"activityDesc":"测试活动","activityId":100001,"activityName":"活动名","createTime":1628424890000,"updateTime":1628424890000}
14:16:03.617 [main] INFO c.l.m.s.defaults.DefaultSqlSession - 执行查询 statement:com.lino.mybatis.test.dao.IActivityDao.queryActivityById parameter:{"activityId":100001}
14:16:03.617 [main] INFO c.l.mybatis.builder.SqlSourceBuilder - 构建参数映射 property:activityId propertyType:class java.lang.Long
14:16:03.617 [main] INFO c.l.m.cache.impl.PerpetualCache - 一级缓存
key:-1917147986:525044925:com.lino.mybatis.test.dao.IActivityDao.queryActivityById:0:2147483647:SELECT activity_id, activity_name, activity_desc, create_time, update_time
FROM activity
where activity_id = ?:100001:development
val:[{"activityDesc":"测试活动","activityId":100001,"activityName":"活动名","createTime":1628424890000,"updateTime":1628424890000}]
14:16:03.617 [main] INFO com.lino.mybatis.test.ApiTest - 测试结果:{"activityDesc":"测试活动","activityId":100001,"activityName":"活动名","createTime":1628424890000,"updateTime":1628424890000}
ApiTest.java
@Test
public void test_queryActivityById() {
// 1.获取映射器对象
IActivityDao dao = sqlSession.getMapper(IActivityDao.class);
// 2.测试验证
Activity activity = new Activity();
activity.setActivityId(100001L);
logger.info("测试结果:{}", JSON.toJSONString(dao.queryActivityById(activity)));
// 提交会话
sqlSession.commit();
logger.info("测试结果:{}", JSON.toJSONString(dao.queryActivityById(activity)));
}
测试结果
14:19:50.865 [main] INFO c.l.m.d.pooled.PooledDataSource - Created connention 1610525991.
拦截SQL:SELECT activity_id, activity_name, activity_desc, create_time, update_time
FROM activity
where activity_id = ?
14:19:50.880 [main] INFO c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:100001
14:19:50.880 [main] INFO com.lino.mybatis.test.ApiTest - 测试结果:{"activityDesc":"测试活动","activityId":100001,"activityName":"活动名","createTime":1628424890000,"updateTime":1628424890000}
14:19:50.880 [main] INFO c.l.m.s.defaults.DefaultSqlSession - 执行查询 statement:com.lino.mybatis.test.dao.IActivityDao.queryActivityById parameter:{"activityId":100001}
14:19:50.880 [main] INFO c.l.mybatis.builder.SqlSourceBuilder - 构建参数映射 property:activityId propertyType:class java.lang.Long
拦截SQL:SELECT activity_id, activity_name, activity_desc, create_time, update_time
FROM activity
where activity_id = ?
14:19:50.880 [main] INFO c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:100001
14:19:50.880 [main] INFO com.lino.mybatis.test.ApiTest - 测试结果:{"activityDesc":"测试活动","activityId":100001,"activityName":"活动名","createTime":1628424890000,"updateTime":1628424890000}
sqlSession.commit()
,执行会话提交,则会清空缓存,此时的二次查询已经不会从缓存中获取数据,而是要去数据库读取。ApiTest.java
@Test
public void test_queryActivityById() {
// 1.获取映射器对象
IActivityDao dao = sqlSession.getMapper(IActivityDao.class);
// 2.测试验证
Activity activity = new Activity();
activity.setActivityId(100001L);
logger.info("测试结果:{}", JSON.toJSONString(dao.queryActivityById(activity)));
// 提交会话
sqlSession.commit();
// 关闭会话
sqlSession.close();
logger.info("测试结果:{}", JSON.toJSONString(dao.queryActivityById(activity)));
}
测试结果
14:22:22.512 [main] INFO c.l.m.d.pooled.PooledDataSource - Created connention 1610525991.
拦截SQL:SELECT activity_id, activity_name, activity_desc, create_time, update_time
FROM activity
where activity_id = ?
14:22:22.512 [main] INFO c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:100001
14:22:22.527 [main] INFO com.lino.mybatis.test.ApiTest - 测试结果:{"activityDesc":"测试活动","activityId":100001,"activityName":"活动名","createTime":1628424890000,"updateTime":1628424890000}
14:22:22.527 [main] INFO c.l.m.d.pooled.PooledDataSource - Returned connection 1610525991 to pool.
14:22:22.527 [main] INFO c.l.m.s.defaults.DefaultSqlSession - 执行查询 statement:com.lino.mybatis.test.dao.IActivityDao.queryActivityById parameter:{"activityId":100001}
14:22:22.527 [main] INFO c.l.mybatis.builder.SqlSourceBuilder - 构建参数映射 property:activityId propertyType:class java.lang.Long
java.lang.RuntimeException: Executor was closed.
at com.lino.mybatis.executor.BaseExecutor.createCacheKey(BaseExecutor.java:177)
at com.lino.mybatis.executor.BaseExecutor.query(BaseExecutor.java:107)
at com.lino.mybatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:54)
at com.lino.mybatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:39)
at com.lino.mybatis.binding.MapperMethod.execute(MapperMethod.java:50)
at com.lino.mybatis.binding.MapperProxy.invoke(MapperProxy.java:36)
at com.sun.proxy.$Proxy4.queryActivityById(Unknown Source)
at com.lino.mybatis.test.ApiTest.test_queryActivityById(ApiTest.java:48)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:76)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
sqlSession.close()
,当执行会话关闭后,调用 rollback(forceRollback)
方法,缓存会被清空,同时因为会话已经关闭,所以再执行的查询会报错:Executor was closed
。