前面我们讲到Mybatis加载相关的配置文件进行初始化,这回我们讲一下一次SQL查询怎么进行的。
准备工作
Mybatis完成一次SQL查询需要使用的代码如下:
String resource = "mybatis.cfg.xml";
Reader reader = Resources.getResourceAsReader(resource);
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = ssf.openSession();
try {
UserInfo user = (UserInfo) session.selectOne("User.selectUser", "1");
System.out.println(user);
} catch (Exception e) {
e.printStackTrace();
} finally {
session.close();
}
本次我们需要进行深入跟踪分析的是:
SqlSession session = ssf.openSession();
UserInfo user = (UserInfo) session.selectOne("User.selectUser", "1");
源码分析
第一步:打开一个会话,我们看看里面具体做了什么事情。
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);
//返回SqlSession的一个默认实例
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();
}
}
这里我们分析一下这里所涉及的步骤:
- 获取前面我们加载配置文件的环境信息,这些环境信息都是包括数据库信息、事务
- 根据环境信息关于事务的配置获取事务工厂
- 通过获取数据源、事务等级、是否自动提交从事务工厂中获取一个事务实例
- 传入事务对象,从配置中获取一个执行器实例,并传入执行器、配置信息等获取一个执行会话对象
从上面的代码我们可以看出,一次配置加载只能有且对应一个数据源。对于上面步骤,我们不难理解,我们重点看看新建执行器和DefaultSqlSession。
首先,我们看看newExecutor到底做了什么?
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;
}
上面代码的执行步骤如下:
- 判断执行器类型,如果配置文件中没有配置执行器类型,则采用默认执行类型ExecutorType.SIMPLE
- 根据执行器类型返回不同类型的执行器(执行器有三种,分别是BatchExecutor,ReuseExecutor,SimpleExecutor)
- 跟执行器绑定拦截器插件
DefaultSqlSession到底是干什么的呢?
DefaultSqlSession实现了SqlSession接口,里面有各种各样的SQL执行方法,主要用于SQL操作的对外接口,它会的调用执行器来执行实际的SQL语句。
接下来我们看看SQL查询是怎么进行的
UserInfo user = (UserInfo) session.selectOne("User.selectUser", "1");
@Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.<T>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;
}
}
实际上调用的是selectList方法(又实际上是调用该类的另一个selectList方法来实现的))
@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("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
selectList执行代码步骤如下:
- 根据SQL的ID到配置文件中找到对应的MappedStatement,在之前配置被加载初始化的时候我们看到了系统会把配置文件中的SQL块解析到一个MappedStatement里面,并将MappedStatement对象放到一个Map里面进行存放,Map的key值就是该SQL块的ID
- 调用执行器的query方法,传入MappedStatement对象、SQL参数对象、范围对象和结果处理方式
好了,目前只剩下一个疑问,那就是执行器到底怎么执行SQL的呢?
上面我们知道了,默认情况下是采用SimpleExecutor执行的,我们看看这个类的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();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
doQuery方法的内部执行步骤:
- 获取配置信息对象
- 通过配置对象获取一个新的StatementHandler,该类主要是用来处理一次SQL操作
- 预处理StatementHandler对象,获取Statement对象
- 传入Statement和结果处理对象,通过StatementHandler的query方法来执行SQL,并对执行结果进行处理
我们看一下newStatementHandler到底做了什么?
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
上面代码的执行步骤:
- 根据相关的参数获取对应的StatementHandler对象。
- 为StatementHandler对象绑定拦截器插件。
RoutingStatementHandler类的构造方法RoutingStatementHandler如下
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
根据 MappedStatement对象的StatementType来创建不同的StatementHandler,这个跟前面执行器的方式类似。StatementType有STATEMENT、PREPARED和CALLABLE三种类型,跟JDBC里面的Statement类型一一对应。
我们看一下prepareStatement方法具体内容:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}