第五章-Mybatis源码解析-执行流程
一切准备就绪,现在开始执行了。一般执行有两种方式:一是直接通过SqlSession中的方法执行;二是通过sqlSession.getMapper(),然后拿到Mapper再通过mapper的接口方法间接执行,如下示例:
SqlSession sqlSession = xmlStart();
Blog blog = sqlSession.selectOne("polyphagic.code.mybatis.BlogMapper.selectBlog1", 1);
System.out.println(blog);
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
blog = mapper.selectBlog1(1);
System.out.println("sqlSession.getMapper 方式:" + blog);
5.1 SqlSession方法直接执行
由于SqlSession中定义的方法较多,这里不会每个都讲,只抽取能代表CRUD的4个方法来讲。
5.1.1 selectOne
Mybatis默认提供的SqlSession实现类是DefaultSqlSession,那直接进入这个类
/**
statement:对应的mapper id,如上文,就是polyphagic.code.mybatis.BlogMapper.selectBlog1
parameter:要传的参数,如上文,此处为1
*/
public <T> T selectOne(String statement, Object parameter) {
// 最终都是返回List
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) { // 只有一个值,只接取出
return list.get(0);
} else if (list.size() > 1) { // 超出一个值就有问题,抛出异常,因为入口方法是只查一个
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
public <E> List<E> selectList(String statement, Object parameter) {
// RowBounds.DEFAULT 设置行边界对象,这是为了按指定偏移量和界限取数据,就好比分页
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 取出先前解析出来的 MappedStatement 对象,解析过程可以看章节`3.2.2.6`
MappedStatement ms = configuration.getMappedStatement(statement);
// 再走到执行器中去执行,继续往里走。这里先介绍一下wrapCollection方法,看关联1
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();
}
}
// 关联1
private Object wrapCollection(final Object object) {
if (object instanceof Collection) {
StrictMap<Object> map = new StrictMap<>();
// 如果是集合类,就以 key = collection 存放
map.put("collection", object);
if (object instanceof List) {
// 如果是 List,就再以 key = list 存放,所以当我们在xml中配置时,传参是list的时候,取 collection 或 list 都可以
map.put("list", object);
}
return map;
} else if (object != null && object.getClass().isArray()) {
StrictMap<Object> map = new StrictMap<>();
// 如果是数组,就以 key = array 存放
map.put("array", object);
return map;
}
return object;
}
进入执行器之前,先看看执行器的继承关系图吧,图是网上找(偷个懒,不想画)
图5-1
SimpleExecutor:一般不带二级缓存的时候用
CachingExecutor:带二级缓存时用
这里只讲上面两个执行器的源码,其他两个执行器,由读者自行扩展。
5.1.1.1 SimpleExecutor执行器
接着上面的源码继续往里走,此时会进到 BaseExecutor.query 方法
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 重点是要取出sql,在mybatis中,sql都用BoundSql表示,继续往下看,看看到底怎么取
BoundSql boundSql = ms.getBoundSql(parameter);
// 创建 CacheKey,下文也有讲解
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 继续查询,看关联1
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
// 关联1 的实现
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.");
}
// 当在一个会话中,首次进来时,并且mapper语句设置flushCache="true"时,才会清除localCache,就是一级缓存了,涉及一二级缓存的,到时单独抽一章来讲,这里暂且知道就行
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
// 先从一级缓存中取,一级缓存只作用于同一个SqlSession
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) { // 缓存中有值
// 处理存储过程的逻辑,这个由读者自行扩展
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else { // 缓存中没值,那就从数据库中去查,看关联2
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--; // 恢复为0
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
// 判断一级缓存范围,默认是session会话级,如果是STATEMENT语句级,那么语句执行就要清除,实际上,这种情况就是没有缓存了
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
// 关联2
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 {
// 从数据库中取到值,具体实现看章节`5.1.1.1-4`
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;
}
走到 MappedStatement.getBoundSql 方法
public BoundSql getBoundSql(Object parameterObject) {
// 这里分别涉及到从 RawSqlSource【里面装饰了 StaticSqlSource(静态sql)】和 DynamicSqlSource(动态sql)中取,关于这两种sql形式,在前面章节`3.2.2.6`
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
// 拿到所有的参数映射,ParameterMapping 在章节`3.2.2.6`的addMappedStatement 方法中生成
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
// 啥参数都不需要的sql,就用下面的方式再重新创建一个
if (parameterMappings == null || parameterMappings.isEmpty()) {
boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
}
// check for nested result maps in parameter mappings (issue #30)
// 这个只有在 parameterMap 中使用,但是 parameterMap 已被废弃,就不做讲解,看源码描述,也是为了解决 (issue #30) 这个bug
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;
}
1.RawSqlSource.getBoundSql
public BoundSql getBoundSql(Object parameterObject) {
// 这里的sqlSource是被 RawSqlSource 装饰的,实际上就是 StaticSqlSource,继续下探
return sqlSource.getBoundSql(parameterObject);
}
// StaticSqlSource 类
public BoundSql getBoundSql(Object parameterObject) {
// 创建一个 BoundSql,BoundSql 构建函数中主要就是赋值操作
return new BoundSql(configuration, sql, parameterMappings, parameterObject);
}
// BoundSql 类,截取部分代码
public class BoundSql {
private final String sql;
private final List<ParameterMapping> parameterMappings;
private final Object parameterObject;
private final Map<String, Object> additionalParameters;
private final MetaObject metaParameters;
public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) {
this.sql = sql;
this.parameterMappings = parameterMappings;
this.parameterObject = parameterObject;
this.additionalParameters = new HashMap<>();
this.metaParameters = configuration.newMetaObject(additionalParameters);
}
}
2.DynamicSqlSource.getBoundSql
3.创建CacheKey
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) { // 验证执行器状态
throw new ExecutorException("Executor was closed.");
}
// 通过以下各对象值的hashcode 组装成 CacheKey,有兴趣的读者可以进去看看update内部实现
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
// 拿出 ParameterMapping
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
// 取出 类型处理器
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// 遍历 parameterMappings
for (ParameterMapping parameterMapping : parameterMappings) {
// 判断不是输出
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
// 属性名,就是#{}内部包裹的名字
String propertyName = parameterMapping.getProperty();
// 额外参数,这块只有 DynamicSqlSource 时才有
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
// parameterObject 就是调用时实际传过来的参数值,如果为null,那value自然也就是null
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
// 如果有自带的类型处理器,就直接赋值给value
value = parameterObject;
} else {
// 自定义类型,那就要从对象中取出对应的属性值,并赋值给 value
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
cacheKey.update(value);
}
}
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
// 最终得到一个 cacheKey
return cacheKey;
}
4 doQuery
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
// 获取 Configuration 对象
Configuration configuration = ms.getConfiguration();
// 拿到 StatementHandler,这个对象就是真正跟jdbc打交道的,封装了jdbc对数据的操作,详情解析看章节`第六章`,以下执行流程都放在`第六章`讲解
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 预处理
stmt = prepareStatement(handler, ms.getStatementLog());
// 执行查询操作,并且会做好结果处理,结果处理将放在`第七章`讲解
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
后续将通过抖音视频/直播的形式分享技术,由于前期要做一些准备和规划,预计2024年6月开始,欢迎关注,如有需要或问题咨询,也可直接抖音沟通交流。