精盡MyBatis源碼分析 – SqlSession 會話與 SQL 執行入口
- 2020 年 11 月 26 日
- 筆記
- mybatis, 源碼解析, 精盡MyBatis源碼分析
該系列文檔是本人在學習 Mybatis 的源碼過程中總結下來的,可能對讀者不太友好,請結合我的源碼注釋(Mybatis源碼分析 GitHub 地址、Mybatis-Spring 源碼分析 GitHub 地址、Spring-Boot-Starter 源碼分析 GitHub 地址)進行閱讀
MyBatis 版本:3.5.2
MyBatis-Spring 版本:2.0.3
MyBatis-Spring-Boot-Starter 版本:2.1.4
SqlSession會話與SQL執行入口
在前面一系列的文檔中,已經詳細的介紹了 MyBatis 的初始化和執行 SQL 的過程,在執行 SQL 的過程中,還存在著一些疑問,例如其中一直提到的 SqlSession 會話在 MyBatis 中是如何被創建的?如何調用 Executor 執行器執行 SQL 的?
那麼接下來就關於上面兩個的兩個問題,一起來探討一下 MyBatis 中 SqlSession 會話的相關內容
先回顧一下《基礎支援層》的Binding模組,每個Mapper Interface
會有對應的MapperProxyFactory
動態代理對象工廠,用於創建MapperProxy
動態代理對象,Mapper 介面中的每個方法都有對應的MapperMethod
對象,該對象會通過 SqlSession 會話執行數據操作
再來看到這張MyBatis整體圖:
在單獨使用 MyBatis 進行資料庫操作時,需要通過SqlSessionFactoryBuilder
構建一個SessionFactory
對象,然後通過它創建一個SqlSession
會話進行資料庫操作
我們通常都會先調用SqlSession
會話的getMapper(Class<T> mapper)
方法,為 Mapper 介面生成一個「實現類」(JDK動態代理對象),然後就可以通過它進行資料庫操作
示例
// <1> 構建 SqlSessionFactory 對象
Reader reader = Resources.getResourceAsReader("org/apache/ibatis/autoconstructor/mybatis-config.xml");
// <2> 默認 DefaultSqlSessionFactory 對象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
// <3> 獲得 SqlSession 對象,默認 DefaultSqlSession 對象
SqlSession sqlSession = sqlSessionFactory.openSession();
// <4> 獲得 Mapper 對象
final AutoConstructorMapper mapper = sqlSession.getMapper(AutoConstructorMapper.class);
// <5> 執行查詢
final Object subject = mapper.getSubject(1);
SqlSessionFactoryBuilder
org.apache.ibatis.session.SqlSessionFactoryBuilder
:構建SqlSessionFactory工廠類,裡面定義了許多build重載方法,主要分為處理Reader和InputStream兩種文件資源對象,程式碼如下:
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(Reader reader) {
return build(reader, null, null);
}
public SqlSessionFactory build(Reader reader, String environment) {
return build(reader, environment, null);
}
public SqlSessionFactory build(Reader reader, Properties properties) {
return build(reader, null, properties);
}
/**
* 構造 SqlSessionFactory 對象
*
* @param reader Reader 對象
* @param environment 環境
* @param properties Properties 變數
* @return SqlSessionFactory 對象
*/
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
/*
* <1> 創建 XMLConfigBuilder 對象
* 會生成一個 XPathParser,包含 Document 對象
* 會創建一個 Configuration 全局配置對象
*/
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
/*
* <2> 解析 XML 文件並配置到 Configuration 全局配置對象中
* <3> 創建 DefaultSqlSessionFactory 對象
*/
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment) {
return build(inputStream, environment, null);
}
public SqlSessionFactory build(InputStream inputStream, Properties properties) {
return build(inputStream, null, properties);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
}
在《MyBatis初始化(一)之載入mybatis-config.xml》中已經分析了,這裡就不再贅述,就是根據文件資源創建Configuration全局配置對象,然後構建一個DefaultSqlSessionFactory對象
DefaultSqlSessionFactory
org.apache.ibatis.session.defaults.DefaultSqlSessionFactory
:實現 SqlSessionFactory 介面,默認的 SqlSessionFactory 實現類
openSession方法
openSession
方法,創建一個DefaultSqlSession
對象,如下:
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
@Override
public SqlSession openSession(boolean autoCommit) {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);
}
@Override
public SqlSession openSession(ExecutorType execType) {
return openSessionFromDataSource(execType, null, false);
}
@Override
public SqlSession openSession(TransactionIsolationLevel level) {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), level, false);
}
@Override
public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) {
return openSessionFromDataSource(execType, level, false);
}
@Override
public SqlSession openSession(ExecutorType execType, boolean autoCommit) {
return openSessionFromDataSource(execType, null, autoCommit);
}
@Override
public SqlSession openSession(Connection connection) {
return openSessionFromConnection(configuration.getDefaultExecutorType(), connection);
}
@Override
public SqlSession openSession(ExecutorType execType, Connection connection) {
return openSessionFromConnection(execType, connection);
}
}
openSession
有很多重載的方法,主要是提供以下幾種入參的支援:
類型 | 參數名稱 | 默認值 | 描述 |
---|---|---|---|
boolean | autoCommit | false | 事務是否自動提交 |
ExecutorType | execType | ExecutorType.SIMPLE | Executor執行器類型 |
TransactionIsolationLevel | level | 無 | 事務隔離級別 |
java.sql.Connection | connection | 無 | 資料庫連接 |
內部直接調用openSessionFromDataSource
私有方法,內部也需要調用openSessionFromConnection
私有方法,如果存在connection
入參,內部則直接調用openSessionFromConnection
私有方法
openSessionFromDataSource方法
openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit)
方法,用於創建一個DefaultSqlSession
對象,方法如下:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level,
boolean autoCommit) {
Transaction tx = null;
try {
// 獲得 Environment 對象
final Environment environment = configuration.getEnvironment();
// 創建 Transaction 對象
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 創建 Executor 對象
final Executor executor = configuration.newExecutor(tx, execType);
// 創建 DefaultSqlSession 對象
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
// 如果發生異常,則關閉 Transaction 對象
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
- 獲得
Environment
當前環境對象 - 通過
getTransactionFactoryFromEnvironment
方法,從 Environment 環境對象中TransactionFactory
對象,用於創建一個Transaction
事務 - 然後再創建一個
Executor
執行器對象 - 根據全局配置對象、執行器和事務是否自動提交創建一個
DefaultSqlSession
對象
openSessionFromConnection方法
openSessionFromConnection(ExecutorType execType, Connection connection)
方法,用於創建一個DefaultSqlSession
對象
和上面的方法邏輯相同,只不過它的入參直接傳入了一個Connection資料庫連接,方法如下:
private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
try {
// 獲得是否可以自動提交
boolean autoCommit;
try {
autoCommit = connection.getAutoCommit();
} catch (SQLException e) {
// Failover to true, as most poor drivers
// or databases won't support transactions
autoCommit = true;
}
// 獲得 Environment 對象
final Environment environment = configuration.getEnvironment();
// 創建 Transaction 對象
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
final Transaction tx = transactionFactory.newTransaction(connection);
// 創建 Executor 對象
final Executor executor = configuration.newExecutor(tx, execType);
// 創建 DefaultSqlSession 對象
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
和上面的方法不同的是,創建Transaction
事務對象時,傳入的參數直接是Connection
資料庫連接對象
getTransactionFactoryFromEnvironment方法
getTransactionFactoryFromEnvironment(Environment environment)
方法,用於創建一個TransactionFactory
對象,在創建 SqlSessionFactory 時,就可以通過設置 Environment 的 DataSource 數據源和 TransactionFactory 事務工廠來集成第三方數據源和事務管理器,程式碼如下:
private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
// 情況一,創建 ManagedTransactionFactory 對象
if (environment == null || environment.getTransactionFactory() == null) {
return new ManagedTransactionFactory();
}
// 情況二,使用 `environment` 中的
return environment.getTransactionFactory();
}
closeTransaction方法
closeTransaction(Transaction tx)
方法,關閉事務,方法如下:
private void closeTransaction(Transaction tx) {
if (tx != null) {
try {
tx.close();
} catch (SQLException ignore) {
// Intentionally ignore. Prefer previous error.
}
}
}
DefaultSqlSession
org.apache.ibatis.session.defaults.DefaultSqlSession
:實現 SqlSession 介面,默認的 SqlSession 實現類,調用 Executor 執行器,執行資料庫操作
構造方法
public class DefaultSqlSession implements SqlSession {
/**
* 全局配置
*/
private final Configuration configuration;
/**
* 執行器對象
*/
private final Executor executor;
/**
* 是否自動提交事務
*/
private final boolean autoCommit;
/**
* 是否發生數據變更
*/
private boolean dirty;
/**
* Cursor 數組
*/
private List<Cursor<?>> cursorList;
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
}
select方法
執行資料庫查詢操作,提供了許多重載方法
@Override
public void select(String statement, Object parameter, ResultHandler handler) {
select(statement, parameter, RowBounds.DEFAULT, handler);
}
@Override
public void select(String statement, ResultHandler handler) {
select(statement, null, RowBounds.DEFAULT, handler);
}
@Override
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
@Override
public <T> T selectOne(String statement) {
return this.selectOne(statement, null);
}
@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.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;
}
}
@Override
public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
return this.selectMap(statement, null, mapKey, RowBounds.DEFAULT);
}
@Override
public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
return this.selectMap(statement, parameter, mapKey, RowBounds.DEFAULT);
}
@Override
public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
// <1> 執行查詢
final List<? extends V> list = selectList(statement, parameter, rowBounds);
// <2> 創建 DefaultMapResultHandler 對象
final DefaultMapResultHandler<K, V> mapResultHandler = new DefaultMapResultHandler<>(mapKey,
configuration.getObjectFactory(), configuration.getObjectWrapperFactory(),
configuration.getReflectorFactory());
// <3> 創建 DefaultResultContext 對象
final DefaultResultContext<V> context = new DefaultResultContext<>();
// <4> 遍歷查詢結果
for (V o : list) {
// 設置 DefaultResultContext 中
context.nextResultObject(o);
// 使用 DefaultMapResultHandler 處理結果的當前元素
mapResultHandler.handleResult(context);
}
// <5> 返回結果
return mapResultHandler.getMappedResults();
}
@Override
public <E> List<E> selectList(String statement) {
return this.selectList(statement, null);
}
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// <1> 獲得 MappedStatement 對象
MappedStatement ms = configuration.getMappedStatement(statement);
// <2> 執行查詢
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();
}
}
上面有很多的資料庫查詢方法,主要分為以下幾種:
select
:執行資料庫查詢操作,通過入參中的ResultHandler
處理結果集,無返回結果selectList
:執行資料庫查詢操作,返回List集合selectOne
:調用selectList
方法,執行資料庫查詢操作,最多只能返回一條數據selectMap
:調用selectList
方法,執行資料庫查詢操作,通過DefaultMapResultHandler
進行處理,將返回結果轉換成Map集合
可以看到通過Executor執行器的query
方法執行查詢操作,可以回顧《SQL執行過程(一)之Executor》中的內容
這裡會先調用wrapCollection
方法對入參進行包裝(如果是集合類型)
update方法
執行資料庫更新操作
@Override
public int insert(String statement) {
return insert(statement, null);
}
@Override
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
@Override
public int delete(String statement) {
return update(statement, null);
}
@Override
public int delete(String statement, Object parameter) {
return update(statement, parameter);
}
@Override
public int update(String statement) {
return update(statement, null);
}
@Override
public int update(String statement, Object parameter) {
try {
// <1> 標記 dirty ,表示執行過寫操作
dirty = true;
// <2> 獲得 MappedStatement 對象
MappedStatement ms = configuration.getMappedStatement(statement);
// <3> 執行更新操作
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
insert
和delete
方法最終都是調用update
方法,通過Executor執行器的update
方法執行資料庫更新操作
這裡會先調用wrapCollection
方法對入參進行包裝(如果是集合類型)
wrapCollection方法
wrapCollection(final Object object)
方法,將集合類型的參數包裝成StrictMap
對象,方法如下:
public static class StrictMap<V> extends HashMap<String, V> {
private static final long serialVersionUID = -5741767162221585340L;
@Override
public V get(Object key) {
if (!super.containsKey(key)) {
throw new BindingException(
"Parameter '" + key + "' not found. Available parameters are " + this.keySet());
}
return super.get(key);
}
}
private Object wrapCollection(final Object object) {
if (object instanceof Collection) {
// 如果是集合,則添加到 collection 中
StrictMap<Object> map = new StrictMap<>();
map.put("collection", object);
// 如果是 List ,則添加到 list 中
if (object instanceof List) {
map.put("list", object);
}
return map;
} else if (object != null && object.getClass().isArray()) {
// 如果是 Array ,則添加到 array 中
StrictMap<Object> map = new StrictMap<>();
map.put("array", object);
return map;
}
return object;
}
getMapper方法
getMapper(Class<T> type)
方法,獲取Mapper介面的代理對象
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
通過Configuration
全局配置對象獲取一個動態代理對象
實際通過MapperRegistry
註冊表獲取到該Mapper介面對應的MapperProxyFactory
動態代理工廠,然後創建一個MapperProxy
動態代理的實例對象
其他方法
flushStatements()
:提交批處理commit()
:提交事務rollback()
:回滾事務close()
:關閉當前會話getConnection()
:獲取當前事務的資料庫連接clearCache()
:清理一級快取
MapperMethod
org.apache.ibatis.binding.MapperMethod
:Mapper介面中定義的方法對應的Mapper方法,通過它來執行SQL
先來看看執行一個SQL的完整流程圖:
在《基礎支援層》的Binding模組已經講過了相關的內容,例如看到前面示例的第4
步,通過DefaultSqlSession
的getMapper
方法會執行以下操作:
- 創建Mapper介面對應的
MapperProxyFactory
動態代理對象工廠 - 通過這個工廠的
newInstance
方法會創建一個MapperProxy
介面代理類,然後返回該Mapper介面的動態代理對象
當你調用這個介面的某個方法時,會進入這個MapperProxy
代理類,我們來看到它的invoke
方法是如何實現的,方法如下:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// <1> 如果是 Object 定義的方法,直接調用
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (method.isDefault()) { // 是否有 default 修飾的方法
// 針對Java7以上版本對動態類型語言的支援
if (privateLookupInMethod == null) {
return invokeDefaultMethodJava8(proxy, method, args);
} else {
return invokeDefaultMethodJava9(proxy, method, args);
}
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// <2.1> 獲得 MapperMethod 對象
final MapperMethod mapperMethod = cachedMapperMethod(method);
// <2.2> 執行 MapperMethod 方法
return mapperMethod.execute(sqlSession, args);
}
首先獲取到方法對應的MapperMethod
對象,然後通過該對象去執行資料庫的操作
execute方法
public Object execute(SqlSession sqlSession, Object[] args) {
// 根據 SqlCommand 的 Type 判斷應該如何執行 SQL 語句
Object result;
switch (command.getType()) {
case INSERT: {
// <1> 獲取參數值與參數名的映射
Object param = method.convertArgsToSqlCommandParam(args);
// <2> 然後通過 SqlSession 進行資料庫更新操作,並將受影響行數轉換為結果
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) { // 無返回,且入參中有 ResultHandler 結果處理器
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
// 執行查詢,返回列表
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
// 執行查詢,返回 Map
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
// 執行查詢,返回 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;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
// 返回結果為 null ,並且返回類型為原始類型(基本類型),則拋出 BindingException 異常
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType()
+ ").");
}
return result;
}
根據 SqlCommand 的 Type 判斷應該如何執行 SQL 語句,類型分為INSERT
、UPDATE
、DELETE
、SELECT
和FLUSH
(進行批處理)五種
前三種都屬於資料庫的更新操作,調用的是DefaultSqlSession
的update
方法,通過rowCountResult(int rowCount)
將受影響行數轉換為返回對象
查詢語句的話就分為以下幾種情況:
- 無返回,且入參中有
ResultHandler
結果處理器,則調用executeWithResultHandler
方法,執行查詢,返回結果設置為null - 返回對象為多條數據,則調用
executeForMany
方法,執行查詢,返回列表 - 返回對象為Map類型,則調用
executeForMap
方法,執行查詢,返回Map - 返回對象為Cursor類型,則調用
executeForCursor
方法,執行查詢,返回Cursor - 返回對象為單個對象,則調用
DefaultSqlSession
的selectOne
方法,執行查詢,返回單個對象
上面執行資料庫操作前會先調用convertArgsToSqlCommandParam
方法,獲取參數值與參數名的映射
convertArgsToSqlCommandParam方法
convertArgsToSqlCommandParam(Object[] args)
方法,根據入參返回參數名稱與入參的映射,方法如下:
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
在《基礎支援層》的反射模組的ParamNameResolver小節已經分析過該方法,可以跳過去看看
rowCountResult方法
rowCountResult(int rowCount)
方法,將受影響行數轉換為結果,方法如下:
private Object rowCountResult(int rowCount) {
final Object result;
if (method.returnsVoid()) {
result = null;
} else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
result = rowCount;
} else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
result = (long) rowCount;
} else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
result = rowCount > 0;
} else {
throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: "
+ method.getReturnType());
}
return result;
}
executeWithResultHandler方法
executeWithResultHandler(SqlSession sqlSession, Object[] args)
方法,通過入參中定義的ResultHandler結果處理器執行查詢,方法如下:
private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
// <1> 獲得 MappedStatement 對象
MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
/*
* <2> 配置校驗
* 因為入參定義了 ResultHandler 結果處理器,所以如果不是存儲過程,且沒有配置返回結果的 Java Type,則會拋出異常
*/
if (!StatementType.CALLABLE.equals(ms.getStatementType())
&& void.class.equals(ms.getResultMaps().get(0).getType())) {
throw new BindingException(
"method " + command.getName() + " needs either a @ResultMap annotation, a @ResultType annotation,"
+ " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");
}
// <3> 獲取參數名稱與入參的映射
Object param = method.convertArgsToSqlCommandParam(args);
// <4> 執行資料庫查詢操作
if (method.hasRowBounds()) { // <4.1> 入參定義了 RowBounds 分頁對象
// <4.1.1> 獲取入參定義了 RowBounds 分頁對象
RowBounds rowBounds = method.extractRowBounds(args);
// <4.1.2> 執行查詢
sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
} else {
// <4.2> 執行查詢
sqlSession.select(command.getName(), param, method.extractResultHandler(args));
}
}
- 獲得
MappedStatement
對象 - 配置校驗,因為入參定義了
ResultHandler
結果處理器,所以如果不是存儲過程,且沒有配置返回結果的 Java Type,則會拋出異常 - 獲取參數名稱與入參的映射
- 執行資料庫查詢操作
- 入參定義了
RowBounds
分頁對象,則獲取該對象,然後執行查詢,傳入分析對象 - 沒有定義則執行執行查詢
- 入參定義了
executeForMany方法
executeForMany(SqlSession sqlSession, Object[] args)
方法,執行查詢,返回列表,方法如下:
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
// 獲取參數名稱與入參的映射
Object param = method.convertArgsToSqlCommandParam(args);
// 執行資料庫查詢操作
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
// 執行查詢,返回 List 集合
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
// 執行查詢,返回 List 集合
result = sqlSession.selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
// 封裝 Array 或 Collection 結果
if (!method.getReturnType().isAssignableFrom(result.getClass())) { // 如果不是 List 集合類型
if (method.getReturnType().isArray()) {
// 將 List 轉換成 Array 數組類型的結果
return convertToArray(result);
} else {
// 轉換成其他 Collection 集合類型的結果
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
// 直接返回 List 集合類型的結果
return result;
}
- 獲取參數名稱與入參的映射
- 執行資料庫查詢操作,獲取到 List 集合結果
- 根據返回結果的類型進行轉換
- 如果是 Array 數組類型,則將 List 轉換成 Array 數組類型的結果
- 如果是其他集合類型,則將 List 轉換成其他 Collection 集合類型的結果
- 否則直接返回 List 集合類型的結果
executeForMap方法
executeForMap(SqlSession sqlSession, Object[] args)
方法,執行查詢,返回Map,方法如下:
private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
Map<K, V> result;
// 獲取參數名稱與入參的映射
Object param = method.convertArgsToSqlCommandParam(args);
// 執行 SELECT 操作
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
// 執行查詢,返回 Map 集合
result = sqlSession.selectMap(command.getName(), param, method.getMapKey(), rowBounds);
} else {
// 執行查詢,返回 Map 集合
result = sqlSession.selectMap(command.getName(), param, method.getMapKey());
}
return result;
}
- 獲取參數名稱與入參的映射
- 執行查詢,返回 Map 集合
總結
本分分析了 SqlSession 會話在 MyBatis 中是如何被創建,如何獲取到 Mapper 介面的動態代理對象,通過該動態代理對象是如何執行 SQL 的
-
SqlSessionFactoryBuilder
構造器提供build
方法,根據mybatis-config.xml
配置文件對 MyBatis 進行初始化,生成Configuration
對象,用於構建一個DefaultSqlSessionFactory
對象 -
通過
1
生成的 SqlSession 工廠對象,我們可以構建一個DefaultSqlSession
會話對象,通過這個會話對象的getMapper(Class<T> mapper)
方法,可以為 Mapper 介面創建一個動態代理對象
(JDK動態代理),其動態代理類為MapperProxy
對象 -
調用 Mapper 介面的某個方法時,會進入相應的
MapperProxy
代理類,根據方法對應的MapperMethod
對象,執行資料庫操作 -
執行資料庫相關操作,調用的就是上面的
DefaultSqlSession
會話對象的相關方法,該會話內部則會通過Executor
執行器去執行資料庫的相關操作,並返回執行結果
好了,對於 MyBatis 整體上所有的內容已經全部分析完了,相信大家對 MyBatis 有了一個全面認識,其中肯定有不對或者迷惑的地方,歡迎指正!!!感謝大家的閱讀!!!😄😄😄
參考文章:芋道源碼《精盡 MyBatis 源碼分析》