上回我们讲到Mybatis加载相关的配置文件进行初始化,这回我们讲一下一次SQL查询怎么进行的。
准备工作
Mybatis完成一次SQL查询需要使用的代码如下:
- String resource = "mybatis.cfg.xml";
- Reader reader = Resources.getResourceAsReader(resource);
- SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(reader);
- <strong> </strong>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");
源码分析
第一步:打开一个会话,我们看看里面具体做了什么事情。
- SqlSession session = ssf.openSession();
DefaultSqlSessionFactory的 openSession()方法内容如下:
- public SqlSession openSession() {
- return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
- }
跟进去,我们看一下openSessionFromDataSource方法到底做了啥:
- private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
- Connection connection = null;
- try {
- final Environment environment = configuration.getEnvironment();
- final DataSource dataSource = getDataSourceFromEnvironment(environment);
- TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
- connection = dataSource.getConnection();
- if (level != null) {
- connection.setTransactionIsolation(level.getLevel());
- }
- connection = wrapConnection(connection);
- Transaction tx = transactionFactory.newTransaction(connection, autoCommit);
- Executor executor = configuration.newExecutor(tx, execType);
- return new DefaultSqlSession(configuration, executor, autoCommit);
- } catch (Exception e) {
- closeConnection(connection);
- throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
- } finally {
- ErrorContext.instance().reset();
- }
- }
这里我们分析一下这里所涉及的步骤:
(1)获取前面我们加载配置文件的环境信息,并且获取环境信息中配置的数据源。
(2)通过数据源获取一个连接,对连接进行包装代理(通过JDK的代理来实现日志功能)。
(3)设置连接的事务信息(是否自动提交、事务级别),从配置环境中获取事务工厂,事务工厂获取一个新的事务。
(4)传入事务对象获取一个新的执行器,并传入执行器、配置信息等获取一个执行会话对象。
从上面的代码我们可以得出,一次配置加载只能有且对应一个数据源。对于上述步骤,我们不难理解,我们重点看看新建执行器和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;
- }
上面代码的执行步骤如下:
(1)判断执行器类型,如果配置文件中没有配置执行器类型,则采用默认执行类型ExecutorType.SIMPLE。
(2)根据执行器类型返回不同类型的执行器(执行器有三种,分别是 BatchExecutor、SimpleExecutor和CachingExecutor,后面我们再详细看看)。
(3)跟执行器绑定拦截器插件(这里也是使用代理来实现)。
DefaultSqlSession到底是干什么的呢?
DefaultSqlSession实现了SqlSession接口,里面有各种各样的SQL执行方法,主要用于SQL操作的对外接口,它会的调用执行器来执行实际的SQL语句。
接下来我们看看SQL查询是怎么进行的
- UserInfo user = (UserInfo) session.selectOne("User.selectUser", "1");
实际调用的是DefaultSqlSession类的selectOne方法,该方法代码如下:
- public Object selectOne(String statement, Object parameter) {
- // Popular vote was to return null on 0 results and throw exception on too many.
- List list = 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方法来实现的):
- public List selectList(String statement, Object parameter) {
- return selectList(statement, parameter, RowBounds.DEFAULT);
- }
- public List 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的执行步骤如下:
(1)根据SQL的ID到配置信息中找对应的MappedStatement,在之前配置被加载初始化的时候我们看到了系统会把配置文件中的SQL块解析并放到一个MappedStatement里面,并将MappedStatement对象放到一个Map里面进行存放,Map的key值是该SQL块的ID。
(2)调用执行器的query方法,传入MappedStatement对象、SQL参数对象、范围对象(此处为空)和结果处理方式。
好了,目前只剩下一个疑问,那就是执行器到底怎么执行SQL的呢?
上面我们知道了,默认情况下是采用SimpleExecutor执行的,我们看看这个类的doQuery方法:
- public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
- Statement stmt = null;
- try {
- Configuration configuration = ms.getConfiguration();
- StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler);
- stmt = prepareStatement(handler);
- return handler.query(stmt, resultHandler);
- } finally {
- closeStatement(stmt);
- }
- }
doQuery方法的内部执行步骤:
(1) 获取配置信息对象。
(2)通过配置对象获取一个新的StatementHandler,该类主要用来处理一次SQL操作。
(3)预处理StatementHandler对象,得到Statement对象。
(4)传入Statement和结果处理对象,通过StatementHandler的query方法来执行SQL,并对执行结果进行处理。
我们看一下newStatementHandler到底做了什么?
- public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) {
- StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler);
- statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
- return statementHandler;
- }
上面代码的执行步骤:
(1)根据相关的参数获取对应的StatementHandler对象。
(2)为StatementHandler对象绑定拦截器插件。
RoutingStatementHandler类的构造方法RoutingStatementHandler如下:
- public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) {
- switch (ms.getStatementType()) {
- case STATEMENT:
- delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler);
- break;
- case PREPARED:
- delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler);
- break;
- case CALLABLE:
- delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler);
- 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) throws SQLException {
- Statement stmt;
- Connection connection = transaction.getConnection();
- //从连接中获取Statement对象
- stmt = handler.prepare(connection);
- //处理预编译的传入参数
- handler.parameterize(stmt);
- return stmt;
- }