您好,登錄后才能下訂單哦!
1. Mapper 代理層執行
Mapper 代理上執行方法調用時,調用被委派給 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); } } // 接口里聲明的方法,轉換為 MapperMethod 來調用 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 根據 mapperInterface.getName() + "." + method.getName() 從 Configuration 對象里找到對應的 MappedStatement ,從而得到要執行的 SQL 操作類型(insert/delete/update/select/flush),然后調用傳入的 sqlSession 實例上的相應的方法。
public Object execute(SqlSession sqlSession, Object[] args) { Object result; if (SqlCommandType.INSERT == command.getType()) { // 把參數轉換為 SqlSession 能處理的格式 Object param = method.convertArgsToSqlCommandParam(args); // 在 sqlSession 上執行并處理結果 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 ,那么這些方法調用會被 SqlSessionInterceptor 攔截,加入與 Spring 事務管理機制協作的邏輯,具體可以看這篇文章MyBatis 事務管理,這里不再展開,最終會調用到 DefaultSqlSession 實例上的方法。
2. 會話層的執行過程
SqlSession 里聲明的所有方法的第一個參數如果是 String statement ,則都是 mapperInterface.getName() + "." + method.getName() ,表示要調用的 SQL 語句的標識符。通過它從 configuration 找到 MappedStatement 。
會話層最主要的邏輯是進行參數的包裝,獲取對應的 MappedStatement ,然后調用持有的 Executor 的方法去執行。
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 執行的過程
我們知道 JDBC 里有三種數據庫語句: java.sql.Statement/PreparedStatement/CallableStatement ,每種語句的執行方式是不一樣的,MyBatis 創建了 StatementHandler 抽象來表示數據庫語句的處理邏輯,有對應的三種具體實現: SimpleStatementHandler/PreparedStatementHandler/CallableStatementHandler 。
RoutingStatementHandler 是個門面模式,構建時根據要執行的數據庫語句類型實例化 SimpleStatementHandler/PreparedStatementHandler/CallableStatementHandler 中的一個類作為目標 delegate,并把調用都轉給這個 delegate 的方法。
不同的 handler 實現實現了對應的:數據庫語句的創建、參數化設置、執行語句。
通過這層抽象,MyBatis 統一了 Executor 里的執行流程,以下以 SimpleExecutor 的流程為例:
1. 對于傳入的 MappedStatement ms ,得到 Configuration configuration 。
2. configuration 通過 ms 的語句類型得到一個 RoutingStatementHandler 的實例(內部有個 delegate 可以委派) handler ,并用 InterceptorChain 對 handler 實例進行裝飾。
3. 通過 SimpleExecutor 持有的 Transaction 實例獲取對應的數據庫連接 connection。
4. handler 通過數據庫連接初始化數據庫語句 java.sql.Statement 或其子類 stmt ,設置超時時間和 fetchSize 。
5. 用 handler 對 stmt 進行參數化處理(比如 PreparedStatement 設置預編譯語句的參數值)。
6. handler 執行相應的 stmt 完成數據庫操作。
7. 用 ResultSetHandler 對結果集進行處理。 ResultSetHandler 會調用 TypeHandler 來進行 Java 類型與數據庫列類型之間轉換。
// 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(); // 創建 handler 來負責具體的執行 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // 創建數據庫語句 stmt = prepareStatement(handler, ms.getStatementLog()); // 執行數據庫操作 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) { // 根據SQL語句的執行方式創建對應的 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 { // 創建數據庫連接 Connection connection = getConnection(statementLog); // 創建數據庫語句 Statement stmt = handler.prepare(connection, transaction.getTimeout()); // 參數化設置 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 { // 由具體的子類來創建對應的 Statement 實例 statement = instantiateStatement(connection); // 通用參數設置 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 的方法來調用 XML 里的 SQL 語句。
Mapper 接口類里可以進行方法重載嗎?
答:不能,因為 MyBatis 里根據 類名 + “.” + 方法名 來查找 SQL 語句,重載會導致這樣的組合出現多條結果。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。