mysql源码分析之执行操作源码分析
- 前言
- mybatis全中文注释版源码
- 底层执行流程源码分析(跟随selectList方法)
- executor.query
- getBoundSql() 一次可执行的 SQL 封装 里面封装需要执行的sql语句和对应位置的参数的ParameterMapping集合
- BoundSql boundSql = ms.getBoundSql(parameterObject)返回的值图片
- createCacheKey该方法主要创建CacheKey 对象用于缓存查询使用 mysqo默认开启一级缓存,当开启二级缓存时会先查询二级缓存是否命中,未命中再查询一级缓存是否命中,未命中再查询数据库。
- 底层query方法 未开启二级缓存
- 底层query方法 开启二级缓存 会先走下面方法 未命中再调用上面方法
- 缓存中全部未命中则会调用queryFromDatabase 从数据库查询出数据
- 从数据库查询出数据doQuery方法
- 总结
前言
我们知道在使用mybatis进行增删改查操作其实底层都是调用的sqlSession接口中的方法,因此本文章主要是里探讨其底层具体的实现细节,由于篇幅有限,因此我在这里就只分析增删改查每种的一个方法,有兴趣可以去下载全文注释的mybatis源码
mybatis全中文注释版源码
底层执行流程源码分析(跟随selectList方法)
/**
* Retrieve a list of mapped objects from the statement key and parameter.
* @param <E> the returned list element type
* @param statement Unique identifier matching the statement to use.
* @param parameter A parameter object to pass to the statement.
* @return List of mapped object
*/
<E> List<E> selectList(String statement, Object parameter);
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 获得 MappedStatement 对象 statement=namespace.id
MappedStatement ms = configuration.getMappedStatement(statement);
// 执行查询
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
/**
* wrapCollection 封装传入的参数
* @param object
* @return
*/
private Object wrapCollection(final Object object) {
if (object instanceof Collection) {
// 如果是集合,则添加到 collection 中
StrictMap<Object> map = new StrictMap<>();
map.put("collection", object);
// 如果是 List ,则添加到 list 中
if (object instanceof List) {
map.put("list", object);
}
return map;
} else if (object != null && object.getClass().isArray()) {
// 如果是 Array ,则添加到 array 中
StrictMap<Object> map = new StrictMap<>();
map.put("array", object);
return map;
}
return object;
}
executor.query
//此方法在SimpleExecutor的父类BaseExecutor中实现
// RowBounds 是分页对象 只有两个属性 开始位置offset limit限制条数
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//根据传入的参数动态获得SQL语句,最后返回用BoundSql对象表示
BoundSql boundSql = ms.getBoundSql(parameter);
//为本次查询创建缓存的Key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 查询
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
getBoundSql() 一次可执行的 SQL 封装 里面封装需要执行的sql语句和对应位置的参数的ParameterMapping集合
// 获得 BoundSql 对象
public BoundSql getBoundSql(Object parameterObject) {
// 获得 BoundSql 对象
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
// 忽略,因为 <parameterMap /> 已经废弃,参见 http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html 文档
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings == null || parameterMappings.isEmpty()) {
boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
}
// check for nested result maps in parameter mappings (issue #30)
// 判断传入的参数中,是否有内嵌的结果 ResultMap 。如果有,则修改 hasNestedResultMaps 为 true
// 存储过程相关,暂时无视
for (ParameterMapping pm : boundSql.getParameterMappings()) {
String rmId = pm.getResultMapId();
if (rmId != null) {
ResultMap rm = configuration.getResultMap(rmId);
if (rm != null) {
hasNestedResultMaps |= rm.hasNestedResultMaps();
}
}
}
return boundSql;
}
// sqlSource.getBoundSql sqlSource里保存者sql语句和需传值的参数对象
@Override
public BoundSql getBoundSql(Object parameterObject) {
// 应用 rootSqlNode
DynamicContext context = new DynamicContext(configuration, parameterObject);
rootSqlNode.apply(context);
// 创建 SqlSourceBuilder 对象
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
// 解析出 SqlSource 对象
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
// 获得 BoundSql 对象
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
// 添加附加参数到 BoundSql 对象中
for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
}
// 返回 BoundSql 对象
return boundSql;
}
BoundSql boundSql = ms.getBoundSql(parameterObject)返回的值图片
createCacheKey该方法主要创建CacheKey 对象用于缓存查询使用 mysqo默认开启一级缓存,当开启二级缓存时会先查询二级缓存是否命中,未命中再查询一级缓存是否命中,未命中再查询数据库。
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 创建 CacheKey 对象
CacheKey cacheKey = new CacheKey();
// 设置 id、offset、limit、sql 到 CacheKey 对象中
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
// 设置 ParameterMapping 数组的元素对应的每个 value 到 CacheKey 对象中
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic 这块逻辑,和 DefaultParameterHandler 获取 value 是一致的。
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);
}
}
// 设置 Environment.id 到 CacheKey 对象中
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
从上述源码中可以知道 判断是否缓存命中的条件主要包括MappedStatement 的id也就是mapper接口的全路径.接口方法+分页对象+sql语句+传入的参数+当前环境的id
底层query方法 未开启二级缓存
@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());
// 已经关闭,则抛出 ExecutorException 异常
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 清空本地缓存,如果 queryStack 为零,并且要求清空本地缓存。
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
// queryStack + 1
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 - 1
queryStack--;
}
if (queryStack == 0) {
// 执行延迟加载
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
// 清空 deferredLoads
deferredLoads.clear();
// 如果缓存级别是 LocalCacheScope.STATEMENT ,则进行清理
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
底层query方法 开启二级缓存 会先走下面方法 未命中再调用上面方法
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 从 MappedStatement 中获取 Cache,注意这里的 Cache 是从MappedStatement中获取的
// 也就是我们上面解析Mapper中<cache/>标签中创建的,它保存在Configration中
// 我们在初始化解析xml时分析过每一个MappedStatement都有一个Cache对象,就是这里
Cache cache = ms.getCache();
// 如果配置文件中没有配置 <cache>,则 cache 为空
if (cache != null) {
//如果需要刷新缓存的话就刷新:flushCache="true"
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
// 暂时忽略,存储过程相关
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
// 从二级缓存中,获取结果
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
// 如果没有值,则执行查询,这个查询实际也是先走一级缓存查询,一级缓存也没有的话,则进行DB查询
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);
}
缓存中全部未命中则会调用queryFromDatabase 从数据库查询出数据
// 从数据库中读取操作
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
// 在缓存中,添加占位对象。此处的占位符,和延迟加载有关,可见 `DeferredLoad#canLoad()` 方法
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方法
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 传入参数创建StatementHanlder对象来执行查询
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 创建jdbc中的statement对象
stmt = prepareStatement(handler, ms.getStatementLog());
// 执行 StatementHandler ,进行读操作
return handler.query(stmt, resultHandler);
} finally {
// 关闭 StatementHandler 对象
closeStatement(stmt);
}
}
总结
以上就是执行一次查询的大致流程
1.调用sqSession的对应方法去执行 获取到MappedStatement对象 也就是一条sql语句对象。可以通过直接调用和生成代理方式的调用
2.将方法的执行交给executor的实现类BaseExecutor或者CachingExecutor去获取BoundSql 和创建 CacheKey先从缓存中获取未命中再从数据库去进行查询 将查询结果再放入缓存中。入宫开启二级缓存会先放入一级缓存中然后再放入二级缓存中。
3.缓存的更新是在增删改时会去执行,会先清空一级缓存然后等待下一次查询时再进行数据缓存。二级缓存的清空是在事务提交时才会清空。在事务被提交前,所有从数据库中查询的结果将缓存在entriesToAddOnCommit集合中,在调commit方法时会调用flushPendingEntries()将 entriesToAddOnCommit、entriesMissedInCache 刷入 delegate(cache) 中。