緣起
在上一章中,我們講述了Mybatis是如何解析它的配置檔案的。在解析的過程中,我們得到了一個至關重要的對象:Configuration對象,那麼我們的sql又是在什麼時候執行的呢?如何執行的呢?讓我們來一起探究
selectOne()
背後的故事
Test test = sqlSession.selectOne("TestMapper.selectTest", 1);
selectOne()
其實是調用的
selectList()
然後判斷,如果結果集大于一條,就報錯:
Expected one result (or null) to be returned by selectOne(), but found:XXX
關鍵代碼如下
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.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;
}
}
selectLis
方法如下
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();
}
}
configuration.getMappedStatement(statement);
就是前往
Configuration
對象的
mappedStatements
屬性中擷取到
MappedStatement
,我們知道
MappedStatement
儲存着我們的
sql
public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
if (validateIncompleteStatements) {
buildAllStatements();
}
return mappedStatements.get(id);
}
拿到了我們的
MappedStatement
之後,想必就是去取出對應的
sql
然後執行了。我們看
executor.query()
方法
該方法最終會調用到
SimpleExecutor
的
doQuery
方法
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.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
上訴
prepareStatement
方法最終會調用到
instantiateStatement
方法,該方法代碼如下
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
return connection.prepareStatement(sql);
} else {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
}
}
看到了吧?從對應的
MapperStatement
對象中取出
BoundSql
對象,然後取出
BoundSql
中的
sql
,然後使用與
JDBC
中相同的方式封裝成一個
Statement
對象。
還記得
JDBC
中的代碼吧?
JDBC
關鍵代碼
PreparedStatement preparedStatement = connection.prepareStatement("select * from test where id = ?");
preparedStatement.setString(1,"1");
ResultSet resultSet = preparedStatement.executeQuery();
這個
connection.prepareStatement
是不是與
Mybatis
源碼中的一模一樣呢?
我們再回到前面的
doQuery
方法,其中有一句代碼
handler.query(stmt, resultHandler);
這個query方法的代碼如下
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps);
}
想必你已經看到了那個
execute()
了,現在明白了吧?
總結
當我們執行
sqlSession.selectOne()
這句代碼時,
Mybatis
會去解析配置檔案的時候就建構好的
Configuration
對象中的
mapperStatements
屬性中通過我們傳入的id取到對應的
MapperStatement
對象,然後從
MapperStatement
對象中取出
BoundSql
對象,然後取出
BoundSql
中的SQL語句,最後使用JDBC的方式執行。