詳解MyBatis Mapper 代理實現(xiàn)數(shù)據(jù)庫調(diào)用原理
1. Mapper 代理層執(zhí)行
Mapper 代理上執(zhí)行方法調(diào)用時,調(diào)用被委派給 MapperProxy 來處理。
public class MapperProxy<T> implements InvocationHandler, Serializable {
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
// 接口里聲明的方法,轉(zhuǎn)換為 MapperMethod 來調(diào)用
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 與 Spring 集成時此處的 sqlSession 仍然 SqlSessionTemplate
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
MapperMethod 根據(jù) mapperInterface.getName() + "." + method.getName() 從 Configuration 對象里找到對應的 MappedStatement ,從而得到要執(zhí)行的 SQL 操作類型(insert/delete/update/select/flush),然后調(diào)用傳入的 sqlSession 實例上的相應的方法。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
if (SqlCommandType.INSERT == command.getType()) {
// 把參數(shù)轉(zhuǎn)換為 SqlSession 能處理的格式
Object param = method.convertArgsToSqlCommandParam(args);
// 在 sqlSession 上執(zhí)行并處理結(jié)果
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
...省略
如果上述方法傳入的是 SqlSessionTemplate ,那么這些方法調(diào)用會被 SqlSessionInterceptor 攔截,加入與 Spring 事務管理機制協(xié)作的邏輯,具體可以看這篇文章MyBatis 事務管理,這里不再展開,最終會調(diào)用到 DefaultSqlSession 實例上的方法。
2. 會話層的執(zhí)行過程
SqlSession 里聲明的所有方法的第一個參數(shù)如果是 String statement ,則都是 mapperInterface.getName() + "." + method.getName() ,表示要調(diào)用的 SQL 語句的標識符。通過它從 configuration 找到 MappedStatement 。
會話層最主要的邏輯是進行參數(shù)的包裝,獲取對應的 MappedStatement ,然后調(diào)用持有的 Executor 的方法去執(zhí)行。
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
private Executor executor;
private boolean autoCommit;
private boolean dirty;
private List<Cursor<?>> cursorList;
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
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();
}
}
3. Executor 執(zhí)行的過程
我們知道 JDBC 里有三種數(shù)據(jù)庫語句: java.sql.Statement/PreparedStatement/CallableStatement ,每種語句的執(zhí)行方式是不一樣的,MyBatis 創(chuàng)建了 StatementHandler 抽象來表示數(shù)據(jù)庫語句的處理邏輯,有對應的三種具體實現(xiàn): SimpleStatementHandler/PreparedStatementHandler/CallableStatementHandler 。
RoutingStatementHandler 是個門面模式,構(gòu)建時根據(jù)要執(zhí)行的數(shù)據(jù)庫語句類型實例化 SimpleStatementHandler/PreparedStatementHandler/CallableStatementHandler 中的一個類作為目標 delegate,并把調(diào)用都轉(zhuǎn)給這個 delegate 的方法。
不同的 handler 實現(xiàn)實現(xiàn)了對應的:數(shù)據(jù)庫語句的創(chuàng)建、參數(shù)化設置、執(zhí)行語句。
通過這層抽象,MyBatis 統(tǒng)一了 Executor 里的執(zhí)行流程,以下以 SimpleExecutor 的流程為例:
1. 對于傳入的 MappedStatement ms ,得到 Configuration configuration 。
2. configuration 通過 ms 的語句類型得到一個 RoutingStatementHandler 的實例(內(nèi)部有個 delegate 可以委派) handler ,并用 InterceptorChain 對 handler 實例進行裝飾。
3. 通過 SimpleExecutor 持有的 Transaction 實例獲取對應的數(shù)據(jù)庫連接 connection。
4. handler 通過數(shù)據(jù)庫連接初始化數(shù)據(jù)庫語句 java.sql.Statement 或其子類 stmt ,設置超時時間和 fetchSize 。
5. 用 handler 對 stmt 進行參數(shù)化處理(比如 PreparedStatement 設置預編譯語句的參數(shù)值)。
6. handler 執(zhí)行相應的 stmt 完成數(shù)據(jù)庫操作。
7. 用 ResultSetHandler 對結(jié)果集進行處理。 ResultSetHandler 會調(diào)用 TypeHandler 來進行 Java 類型與數(shù)據(jù)庫列類型之間轉(zhuǎn)換。
// SimpleExecutor
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();
// 創(chuàng)建 handler 來負責具體的執(zhí)行
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 創(chuàng)建數(shù)據(jù)庫語句
stmt = prepareStatement(handler, ms.getStatementLog());
// 執(zhí)行數(shù)據(jù)庫操作
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
// Configuration
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;
}
// RoutingStatementHandler
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 根據(jù)SQL語句的執(zhí)行方式創(chuàng)建對應的 handler 實例
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());
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
// 創(chuàng)建數(shù)據(jù)庫連接
Connection connection = getConnection(statementLog);
// 創(chuàng)建數(shù)據(jù)庫語句
Statement stmt = handler.prepare(connection, transaction.getTimeout());
// 參數(shù)化設置
handler.parameterize(stmt);
return stmt;
}
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
// BaseStatementHandler
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// 由具體的子類來創(chuàng)建對應的 Statement 實例
statement = instantiateStatement(connection);
// 通用參數(shù)設置
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
// PreparedStatementHandler
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() != null) {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
} else {
return connection.prepareStatement(sql);
}
}
// PreparedStatementHandler
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
4. 問題
只在 XML 里定義 SQL、沒有對應的 Java 接口類能否使用 MyBatis ?
答:可以,通過 SqlSession 的方法來調(diào)用 XML 里的 SQL 語句。
Mapper 接口類里可以進行方法重載嗎?
答:不能,因為 MyBatis 里根據(jù) 類名 + “.” + 方法名 來查找 SQL 語句,重載會導致這樣的組合出現(xiàn)多條結(jié)果。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
java書店系統(tǒng)畢業(yè)設計 總體設計(1)
這篇文章主要介紹了java書店系統(tǒng)畢業(yè)設計,第一步系統(tǒng)總體設計,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-10-10
ApiOperation和ApiParam注解依賴的安裝和使用以及注意事項說明
這篇文章主要介紹了ApiOperation和ApiParam注解依賴的安裝和使用以及注意事項說明,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-09-09
全面匯總SpringBoot和SpringClould常用注解
Java注解是附加在代碼中的一些元信息,用于一些工具在編譯、運行時進行解析和使用,起到說明、配置的功能,這篇文章就帶你來了解一下2021-08-08
idea熱部署插件jrebel正式版及破解版安裝詳細圖文教程
這篇文章主要介紹了idea熱部署插件jrebel正式版及破解版安裝詳細教程,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-12-12

