詳解MyBatis Mapper 代理實(shí)現(xiàn)數(shù)據(jù)庫(kù)調(diào)用原理
1. Mapper 代理層執(zhí)行
Mapper 代理上執(zhí)行方法調(diào)用時(shí),調(diào)用被委派給 MapperProxy 來(lái)處理。
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 來(lái)調(diào)用 final MapperMethod mapperMethod = cachedMapperMethod(method); // 與 Spring 集成時(shí)此處的 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 對(duì)象里找到對(duì)應(yīng)的 MappedStatement ,從而得到要執(zhí)行的 SQL 操作類型(insert/delete/update/select/flush),然后調(diào)用傳入的 sqlSession 實(shí)例上的相應(yīng)的方法。
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)用會(huì)被 SqlSessionInterceptor 攔截,加入與 Spring 事務(wù)管理機(jī)制協(xié)作的邏輯,具體可以看這篇文章MyBatis 事務(wù)管理,這里不再展開(kāi),最終會(huì)調(diào)用到 DefaultSqlSession 實(shí)例上的方法。
2. 會(huì)話層的執(zhí)行過(guò)程
SqlSession 里聲明的所有方法的第一個(gè)參數(shù)如果是 String statement ,則都是 mapperInterface.getName() + "." + method.getName() ,表示要調(diào)用的 SQL 語(yǔ)句的標(biāo)識(shí)符。通過(guò)它從 configuration 找到 MappedStatement 。
會(huì)話層最主要的邏輯是進(jìn)行參數(shù)的包裝,獲取對(duì)應(yīng)的 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í)行的過(guò)程
我們知道 JDBC 里有三種數(shù)據(jù)庫(kù)語(yǔ)句: java.sql.Statement/PreparedStatement/CallableStatement ,每種語(yǔ)句的執(zhí)行方式是不一樣的,MyBatis 創(chuàng)建了 StatementHandler 抽象來(lái)表示數(shù)據(jù)庫(kù)語(yǔ)句的處理邏輯,有對(duì)應(yīng)的三種具體實(shí)現(xiàn): SimpleStatementHandler/PreparedStatementHandler/CallableStatementHandler 。
RoutingStatementHandler 是個(gè)門(mén)面模式,構(gòu)建時(shí)根據(jù)要執(zhí)行的數(shù)據(jù)庫(kù)語(yǔ)句類型實(shí)例化 SimpleStatementHandler/PreparedStatementHandler/CallableStatementHandler 中的一個(gè)類作為目標(biāo) delegate,并把調(diào)用都轉(zhuǎn)給這個(gè) delegate 的方法。
不同的 handler 實(shí)現(xiàn)實(shí)現(xiàn)了對(duì)應(yīng)的:數(shù)據(jù)庫(kù)語(yǔ)句的創(chuàng)建、參數(shù)化設(shè)置、執(zhí)行語(yǔ)句。
通過(guò)這層抽象,MyBatis 統(tǒng)一了 Executor 里的執(zhí)行流程,以下以 SimpleExecutor 的流程為例:
1. 對(duì)于傳入的 MappedStatement ms ,得到 Configuration configuration 。
2. configuration 通過(guò) ms 的語(yǔ)句類型得到一個(gè) RoutingStatementHandler 的實(shí)例(內(nèi)部有個(gè) delegate 可以委派) handler ,并用 InterceptorChain 對(duì) handler 實(shí)例進(jìn)行裝飾。
3. 通過(guò) SimpleExecutor 持有的 Transaction 實(shí)例獲取對(duì)應(yīng)的數(shù)據(jù)庫(kù)連接 connection。
4. handler 通過(guò)數(shù)據(jù)庫(kù)連接初始化數(shù)據(jù)庫(kù)語(yǔ)句 java.sql.Statement 或其子類 stmt ,設(shè)置超時(shí)時(shí)間和 fetchSize 。
5. 用 handler 對(duì) stmt 進(jìn)行參數(shù)化處理(比如 PreparedStatement 設(shè)置預(yù)編譯語(yǔ)句的參數(shù)值)。
6. handler 執(zhí)行相應(yīng)的 stmt 完成數(shù)據(jù)庫(kù)操作。
7. 用 ResultSetHandler 對(duì)結(jié)果集進(jìn)行處理。 ResultSetHandler 會(huì)調(diào)用 TypeHandler 來(lái)進(jìn)行 Java 類型與數(shù)據(jù)庫(kù)列類型之間轉(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 來(lái)負(fù)責(zé)具體的執(zhí)行 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // 創(chuàng)建數(shù)據(jù)庫(kù)語(yǔ)句 stmt = prepareStatement(handler, ms.getStatementLog()); // 執(zhí)行數(shù)據(jù)庫(kù)操作 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語(yǔ)句的執(zhí)行方式創(chuàng)建對(duì)應(yīng)的 handler 實(shí)例 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ù)庫(kù)連接 Connection connection = getConnection(statementLog); // 創(chuàng)建數(shù)據(jù)庫(kù)語(yǔ)句 Statement stmt = handler.prepare(connection, transaction.getTimeout()); // 參數(shù)化設(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 { // 由具體的子類來(lái)創(chuàng)建對(duì)應(yīng)的 Statement 實(shí)例 statement = instantiateStatement(connection); // 通用參數(shù)設(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. 問(wèn)題
只在 XML 里定義 SQL、沒(méi)有對(duì)應(yīng)的 Java 接口類能否使用 MyBatis ?
答:可以,通過(guò) SqlSession 的方法來(lái)調(diào)用 XML 里的 SQL 語(yǔ)句。
Mapper 接口類里可以進(jìn)行方法重載嗎?
答:不能,因?yàn)?MyBatis 里根據(jù) 類名 + “.” + 方法名 來(lái)查找 SQL 語(yǔ)句,重載會(huì)導(dǎo)致這樣的組合出現(xiàn)多條結(jié)果。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
java實(shí)現(xiàn)List中對(duì)象排序的方法
這篇文章主要介紹了java實(shí)現(xiàn)List中對(duì)象排序的方法,涉及Java中的遍歷與對(duì)象操作技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11Java實(shí)現(xiàn)解出世界最難九宮格問(wèn)題
這篇文章主要介紹了Java實(shí)現(xiàn)解出世界最難九宮格問(wèn)題,芬蘭數(shù)學(xué)家因卡拉花費(fèi)3個(gè)月設(shè)計(jì)出了世界上迄今難度最大的數(shù)獨(dú)游戲,而且它只有一個(gè)答案,本文使用Java實(shí)現(xiàn)解出,需要的朋友可以參考下2015-01-01java書(shū)店系統(tǒng)畢業(yè)設(shè)計(jì) 總體設(shè)計(jì)(1)
這篇文章主要介紹了java書(shū)店系統(tǒng)畢業(yè)設(shè)計(jì),第一步系統(tǒng)總體設(shè)計(jì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10ApiOperation和ApiParam注解依賴的安裝和使用以及注意事項(xiàng)說(shuō)明
這篇文章主要介紹了ApiOperation和ApiParam注解依賴的安裝和使用以及注意事項(xiàng)說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09Java Benchmark 基準(zhǔn)測(cè)試的實(shí)例詳解
這篇文章主要介紹了Java Benchmark 基準(zhǔn)測(cè)試的實(shí)例詳解的相關(guān)資料,這里提供實(shí)例幫助大家學(xué)習(xí)理解這部分內(nèi)容,需要的朋友可以參考下2017-08-08全面匯總SpringBoot和SpringClould常用注解
Java注解是附加在代碼中的一些元信息,用于一些工具在編譯、運(yùn)行時(shí)進(jìn)行解析和使用,起到說(shuō)明、配置的功能,這篇文章就帶你來(lái)了解一下2021-08-08idea熱部署插件jrebel正式版及破解版安裝詳細(xì)圖文教程
這篇文章主要介紹了idea熱部署插件jrebel正式版及破解版安裝詳細(xì)教程,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12Javaweb基礎(chǔ)入門(mén)requse原理與使用
Request對(duì)象的作用是與客戶端交互,收集客戶端的Form、Cookies、超鏈接,或者收集服務(wù)器端的環(huán)境變量,接下來(lái)本篇將詳細(xì)講述2021-11-11關(guān)于@Autowired的使用及注意事項(xiàng)
這篇文章主要介紹了關(guān)于@Autowired的使用及注意事項(xiàng),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05