水煮MyBatis(四)- 方法执行过程
前言
接上章,容器启动之后,给mapper接口中的方法生成了statement,就是为了下一步执行的时候,直接取用,这一章就详细聊聊这块的源代码。与标准的SQL语句不同,mybatis里参数的赋值是通过#或者$包裹的变量名称。
解析案例
@Autowired
private ImageInfoMapper imageInfoMapper;
@Test
public void select() {
ImageInfo info = imageInfoMapper.byMd5("6e705a7733ac5gbwopmp02");
log.info(">>>>>>>>>>>>>>>>>>>=>,{}", info);
}
序列图
MapperMethod对应的是Mapper里的一个方法,把方法名和命令类型的信息保存到容器上下文。
执行过程
几个主要的类,在上面的序列图中有展示,这里就按照这个流程,捡几个关键点聊一下。
execute方法
因为上面的例子是查询单个结果,所以这里忽略其他的命令类型,只展示selectOne方法。源代码比较简单,是根据命令类型
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: // 忽略展示
case UPDATE:// 忽略展示
case DELETE: // 忽略展示
case SELECT:
if (...)
// 忽略展示其他查询场景,比如查询多个结果,executeForMany(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
// 查询单个结果
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
return result;
}
其中MapperMethod是启动注册Mapper时,给方法生成的一个对象,数据如下:
执行拦截器
可以说,这就是方法主体了,主要有三个步骤:
- 生成sqlSession;
- 执行方法;
- 关闭sqlSession;
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 生成sqlSession
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
// 执行具体方法
return method.invoke(sqlSession, args);
} catch (Throwable t) {
...
} finally {
if (sqlSession != null) {
// 关闭sqlSession
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
获取session
从一系列代码中,可以看出session中包含了事务信息、执行器、运行环境信息、是否自动提交等关键数据。其中Executor有多种类型,批量执行器、可重用执行器、默认执行器,这里获取的就是默认执行器。
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
// 事务信息
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 最终指向执行上下文
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
创建sqlsession经过了以下几个主要步骤:
- 从配置中获取Environment;
- 从Environment中取得DataSource;
- 从Environment中取得TransactionFactory;
- 从DataSource里获取数据库连接对象Connection;
- 在取得的数据库连接上创建事务对象Transaction;
- 创建Executor对象(该对象非常重要,事实上sqlsession的所有操作都是通过它完成的);
- 创建sqlsession对象。
查询逻辑
这里也比较简单,查询列表数据,从启动时注册好的上下文环境中【前一章介绍的】,取出对应的MappedStatement,下个段落的BoundSql对象,也是从这里取出的。
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
获取绑定的sql
从configuration对象中获取boundSql对象【StaticSqlSource】,也是在解析Mapper时就已经处理好的方法信息。
// 从MappedStatement中获取
BoundSql boundSql = ms.getBoundSql(parameterObject);
public BoundSql getBoundSql(Object parameterObject) {
// 从当前对象中组织属性信息,返回BoundSql对象
return new BoundSql(configuration, sql, parameterMappings, parameterObject);
}
执行细节
这里已经到了底层执行细节,可以看出和jdbc里执行PrepareStatement对象顺序是一致的
- 用sql作为参数,创建PreparedStatement对象;
- 给sql中的参数占位符设置数值;
- 执行查询;
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
// 获取mybatis配置信息,主要是获取其中的datasource对象
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 设置参数
stmt = prepareStatement(handler, ms.getStatementLog());
// 这里的query方法主要是 stmt.execute(),和JDBC一致
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}