上文我们简单描述了Mybatis从配置到Sql执行的步骤,本文将从获取SqlSession开始,通过获取Mapper,最终分析Mapper方法的执行流程。我们将借助源码逐步探索这一过程。
// 通过sqlSessionFactory获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 进入openSession()
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 1.获取配置环境
final Environment environment = configuration.getEnvironment();
// 2.获取事务工厂
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 3.创建新的事务
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 4.获取执行器
final Executor executor = configuration.newExecutor(tx, execType);
// 5.并返回默认的SqlSession
return new DefaultSqlSession(configuration, executor, autoCommit);
...
}
1-3步骤数据源配置信息都是之前加载全局文件mybatis-config.xml解析好放入了configuration对象中,如下图:
// 下面来看获取执行器的逻辑
// 传入参数tx(事务)+ execType (执行类型,SIMPLE, REUSE, BATCH)
final Executor executor = configuration.newExecutor(tx, execType);
// ->
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);
}
// 对执行器应用拦截器链中的所有拦截器
executor = (Executor) interceptorChain.pluginAll(executor);
// 返回最终的执行器
return executor;
}
// 我们这边的例子是个简单类型,所以创建的是SimpleExecutor
// 并且cacheEnabled默认是打开的(解析settings节点的时候的默认值)
// 所以这里最终的Executor为CachingExecutor
// 最终构建的SqlSession为DefaultSqlSession
return new DefaultSqlSession(configuration, executor, autoCommit);
// 通过sqlSession获取UserMapper
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// ->
// 构建sqlSession的时候会有configuration,mapper信息也都提前解析到configuration对象中了
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
// ->
// 使用了动态代理来创建UserMapper接口的实现
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 获取给定类型的 MapperProxyFactory
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
// 如果未找到对应的 MapperProxyFactory,抛出异常
if (mapperProxyFactory == null) {
throw new BindingException("类型 " + type + " 在 MapperRegistry 中未知。");
}
try {
// 创建并返回 Mapper 接口的代理实例
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
// 抛出异常,指明获取 Mapper 实例时出错原因
throw new BindingException("获取 Mapper 实例时出错。原因:" + e, e);
}
}
// ->
public T newInstance(SqlSession sqlSession) {
// 创建一个 MapperProxy 实例,用于代理 Mapper 接口的方法调用
// sqlSession:用于执行数据库操作的 SqlSession 实例
// mapperInterface:要代理的 Mapper 接口类型
// methodCache:缓存 Mapper 方法的信息
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
// 通过 MapperProxy 创建并返回 Mapper 接口的代理实例
return newInstance(mapperProxy);
}
// 见下图返回的是个代理对象
// userMapper执行方法
User user = userMapper.selectById(1);
// 调用Mapper接口的所有方法都会先调用到MapperProxy这个代理类的invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 如果调用的方法是 Object 类的方法,则直接执行
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// 如果调用的方法是默认方法(default method),则执行默认方法
else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
// 捕获并重新抛出可能出现的异常,将其转换为运行时异常
throw ExceptionUtil.unwrapThrowable(t);
}
// 根据方法获取相应的 MapperMethod,然后执行该方法
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
先来看 cachedMapperMethod(method);
final MapperMethod mapperMethod = cachedMapperMethod(method);
// ->
// 这里构造了MapperMethod对象,里面包括执行的 SQL 命令、参数映射、结果处理等,以及其他与数据库操作相关的配置。这些信息使得 MyBatis 能够在执行 Mapper 接口方法时,根据配置来构造和执行对应的 SQL 操作
private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
再来看mapperMethod.execute(sqlSession, args);
return mapperMethod.execute(sqlSession, args);
// ->
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// 根据 SQL 命令类型进行处理
switch (command.getType()) {
// 对于 INSERT 类型的命令
case INSERT: {
// 将方法参数转换为对应的 SQL 命令参数
Object param = method.convertArgsToSqlCommandParam(args);
// 执行插入操作并获取影响的行数
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
// 对于 UPDATE 类型的命令
case UPDATE: {
// 将方法参数转换为对应的 SQL 命令参数
Object param = method.convertArgsToSqlCommandParam(args);
// 执行更新操作并获取影响的行数
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
// 对于 DELETE 类型的命令
case DELETE: {
// 将方法参数转换为对应的 SQL 命令参数
Object param = method.convertArgsToSqlCommandParam(args);
// 执行删除操作并获取影响的行数
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
// 对于 SELECT 类型的命令
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
// 对于返回值为 void 且使用了 ResultHandler 的情况,执行查询并使用 ResultHandler 处理结果
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
// 对于返回多个结果的情况,执行查询返回列表
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
// 对于返回 Map 结果的情况,执行查询返回 Map
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
// 对于返回 Cursor 结果的情况,执行查询返回 Cursor
result = executeForCursor(sqlSession, args);
} else {
// 对于其他情况,执行查询返回单个结果
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
// 对于 FLUSH 类型的命令
case FLUSH:
// 执行缓存刷新操作
result = sqlSession.flushStatements();
break;
default:
// 抛出异常,表示不支持的执行方法
throw new BindingException("未知的执行方法:" + command.getName());
}
// 对于返回值为基本类型的情况,不允许返回 null
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper 方法 '" + command.getName()
+ "' 在使用基本类型返回时尝试返回了 null(返回类型:" + method.getReturnType() + ")。");
}
// 返回执行结果
return result;
}
现在直接来到我们当前sql的代码块
// args 是传递给代理方法的参数数组;param将被用作SQL命令的参数
// command.getName() 获取了与方法对应的SQL命令的 id(通常对应 XML 配置文件中的 id 属性)
result = sqlSession.selectOne(command.getName(), param);
// ->
// 1.调用 selectList 方法执行查询操作,获取结果列表。
// 2.如果结果列表中有且仅有一个元素,则返回该元素作为查询结果。
// 3.如果结果列表中有多个元素,则抛出 TooManyResultsException 异常,表示查询结果过多。
// 4.如果结果列表为空,则返回 null,表示没有查询到结果。
@Override
public <T> T selectOne(String statement, Object parameter) {
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
}
else if (list.size() > 1) {
throw new TooManyResultsException("在 selectOne() 中期望返回一个结果(或 null),但发现了多个结果:" + list.size());
}
else {
return null;
}
}
// -> this.selectList(statement, parameter);
// statement:要执行的 SQL 语句的唯一标识(对应 XML 映射文件中的 id 属性)。
// parameter:传递给 SQL 语句的参数。
// rowBounds:用于限制结果集返回的行数
// 主要步骤:
// 1.通过调用 configuration.getMappedStatement 获取被映射的 SQL 语句信息。
// 2.使用 executor 执行查询操作,executor.query 方法接受 SQL 语句信息、参数、行数限制和结果处理器等参数,返回结果列表。
// 3.在执行过程中,如果出现异常,将异常重新包装为运行时异常,抛出并指明错误原因。
// 4.无论是否发生异常,最后会重置 MyBatis 的错误上下文。
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("查询数据库时出错。原因:" + e, e);
} finally {
ErrorContext.instance().reset();
}
}
// 执行器执行executor.query(..)
// 又上文知道这里执行器被包装过,所有最外层是CachingExecutor
// ->
// ms:被映射的 SQL 语句信息,包含了 SQL 语句、参数映射等信息。
// parameter:传递给 SQL 语句的参数。
// rowBounds:用于限制结果集返回的行数。
// resultHandler:用于处理查询结果的结果处理器。
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 获取BoundSql 主要包含以下信息:
// 1.sql:这是包含占位符的原始的SQL语句
// 2.parameterMappings:包含了参数映射信息的列表
// 3.parameterObject:传递给SQL语句的参数对象
// 4.additionalParameters:额外的参数映射
// 5.metaParameters:用于访问和操作 Java 对象属性的工具类
BoundSql boundSql = ms.getBoundSql(parameter);
// 创建用于缓存的 CacheKey
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 调用 query 方法执行查询操作,并返回结果列表
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
进入查询操作query(…)
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 获取 MappedStatement 对应的缓存
Cache cache = ms.getCache();
// 如果存在缓存
if (cache != null) {
// 根据需要刷新缓存
flushCacheIfRequired(ms);
// 如果 MappedStatement 配置了使用缓存,并且没有结果处理器
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);
// 将结果列表放入缓存中(issue #578 and #116)
tcm.putObject(cache, key, list);
}
return list;
}
}
// 如果不满足缓存条件,直接执行查询操作
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
第一次缓存肯定没有值,所以直接查询delegate.query(…)
// 一直往下跟,直接来到查询代码
// ->
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// ->
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
// ->
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
// -> doQuery(...)
// 综合来看,这段代码负责执行 SQL 查询操作,并将查询结果交给结果处理器进行处理,最终返回处理后的结果列表。
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
// 获取 MappedStatement 的配置信息
Configuration configuration = ms.getConfiguration();
// 创建 StatementHandler 对象,用于处理 SQL 语句
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 准备 Statement 对象,并记录日志
stmt = prepareStatement(handler, ms.getStatementLog());
// 调用 StatementHandler 的 query 方法执行查询操作
return handler.query(stmt, resultHandler);
} finally {
// 在 finally 中关闭 Statement 对象
closeStatement(stmt);
}
}