背景
介紹ibatis實現之前,先來看一段jdbc代碼:
- Class.forName("com.mysql.jdbc.Driver");
- String url = "jdbc:mysql://localhost:3306/learnworld";
- Connection con = DriverManager.getConnection(url, "root","learnworld");
- String sql = "select * from test";
- PreparedStatement ps = con.prepareStatement(sql);
- ResultSet rs = ps.executeQuery();
- while(rs.next()){
- System.out.println("id=" + rs.getInt(1)+". score=" + rs.getInt(2));
- }
上面這段代碼大家比較熟悉,這是一個典型的jdbc方式處理流程: 建立連接->傳遞參數->sql執行->處理結果->關閉連接。
問題
上面的代碼中包含了很多不穩定的因素,可能會經常發生修改:
1. 數據源和事務管理等
2. sql語句
3. 入參和結果處理
如果這些發生變化,我們需要直接修改代碼,重新編譯、打包、發布等。
DIY
下面從我們自己DIY的視角來考慮如何設計框架,應對這些問題。框架的核心思想是抽取共性,封裝可能出現的變化。
如何處理數據源變化?
將數據源連接等過程固定,將數據源中易變的信息封裝在一起(如driverClassName, url等),放在配置文件中。
如何處理sql語句的變化?
將sql語句統一放在配置文件中,為每條sql語句設置標識,在代碼中使用標識進行調用。
如何應對入參和結果處理的變化?
將參數傳遞,結果映射到java bean等統一封裝在配置文件中。
結論: 將不變的流程固化到代碼中,將變化的信息封裝在配置文件中。
核心接口
ibatis抽取了以下幾個重要接口:
1. SqlMapExecutor
該接口是對SQL操作行為的抽象,提供了SQL單條執行和批處理涉及的所有操作方法。
2. SqlMapTransactionManager
該接口是對事務行為的抽象,提供了事務執行過程中的涉及的所有方法。
3. SqlMapClient
該接口定位是SQL執行客戶端,是線程安全的,用於處理多個線程的sql執行。它繼承了上面兩個接口,這意味着該接口具有SQL執行、批處理和事 務處理的能力,如果你擁有該接口的實現類,意味着執行任何SQL語句對你來說是小菜一碟。該接口的核心實現類是SqlMapClientImpl。
4. SqlMapSession
該接口在繼承關系上和SqlMapClient一致,但它的定位是保存單線程sql執行過程的session信息。該接口的核心實現類是SqlMapSessionImpl。
5. MappedStatement
該接口定位是單條SQL執行時的上下文環境信息,如SQL標識、SQL、參數信息、返回結果、操作行為等。
6. ParameterMap/ResultMap
該接口用於在SQL執行的前后提供參數准備和執行結果集的處理。
以上接口的類圖關系如下(部分):
這里必須要強調SqlMapExecutorDelegate這個類,他是一個執行代理類,在ibatis框架中地位非常重要,因為他耦合了用戶端的操作行為和執行環境,他持有執行操作的所需要的所有數據,同時管理着執行操作依賴的環境。
初始化過程
1. 讀取和解析sqlmap配置文件。
2. 注冊Statement對象。
3. 創建SqlMapClientImpl對象。
下面是一個Spring中sqlMapClient的bean配置:
- <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
- <property name="dataSource" ref="dataSource" />
- <property name="configLocation" value="classpath/sqlmap/sqlmap-ibatis.xml" />
- </bean>
下面看一下SqlMapClientFactoryBean的初始化方法afterPropertiesSet(),用於構建sqlMapClient對象:
- public void afterPropertiesSet()throws Exception {
- ...
- this.sqlMapClient = buildSqlMapClient(this.configLocations,this.mappingLocations, this.sqlMapClientProperties); //初始化核心方法,構建sqlMapClient對象
- ...
- }
看一下buildSqlMapClient()的實現:
- protected SqlMapClient buildSqlMapClient(
- Resource[] configLocations, Resource[] mappingLocations, Properties properties)
- throws IOException {
- ...
- SqlMapClient client = null;
- SqlMapConfigParser configParser = new SqlMapConfigParser();
- for (int i =0; i < configLocations.length; i++) {
- InputStream is = configLocations[i].getInputStream();
- try {
- client = configParser.parse(is, properties); //通過SqlMapConfigParser解析配置文件,生成SQLMapClientImpl對象
- }
- catch (RuntimeException ex) {
- throw new NestedIOException("Failed to parse config resource: " + configLocations[i], ex.getCause());
- }
- }
- ...
- return client;
- }
初始化的核心是通過配置文件構建SQLMapClientImpl對象和其內部的各個屬性,這里就不詳述了,詳細構建過程將另文分析。
SQL執行過程
下面以一個select語句的執行過程,分析一下以上各ibatis核心接口相互如何配合。
1. dao調用SqlMapClientTemplate的query()方法:
- public Object queryForObject(final String statementName,final Object parameterObject)
- throws DataAccessException {
- return execute(new SqlMapClientCallback() {
- public Object doInSqlMapClient(SqlMapExecutor executor)throws SQLException {
- return executor.queryForObject(statementName, parameterObject);
- }
- });
- }
2. execute()方法中展示了執行過程中的核心邏輯:獲取session -> 獲取connection -> 執行sql ->釋放connection -> 關閉session。
部分源碼如下:
- public Object execute(SqlMapClientCallback action)throws DataAccessException {
- ...
- SqlMapSession session = this.sqlMapClient.openSession();//獲取session信息
- Connection ibatisCon = null; //獲取Connection
- try {
- Connection springCon = null;
- DataSource dataSource = getDataSource();
- boolean transactionAware = (dataSourceinstanceof TransactionAwareDataSourceProxy);
- try {
- ibatisCon = session.getCurrentConnection();
- if (ibatisCon == null) {
- springCon = (transactionAware ?
- dataSource.getConnection() : DataSourceUtils.doGetConnection(dataSource));
- session.setUserConnection(springCon);
- ...
- }
- }
- ...
- // Execute given callback...
- try {
- return action.doInSqlMapClient(session);//執行SQL
- }
- ...
- finally {
- // 關閉Connection
- try {
- if (springCon !=null) {
- if (transactionAware) {
- springCon.close();
- }
- else {
- DataSourceUtils.doReleaseConnection(springCon, dataSource);
- }
- }
- }
- if (ibatisCon == null) {
- session.close(); //關閉session
- }
- }
3. action.doInSqlMapClient(session)調用SqlMapSessionImpl().queryForObject()方法。
注意: 這里調用對象主體為SqlMapSessionImpl,表示在線程session中執行sql(session是ThreadLocal的),而不在負責整體協調的SqlMapClientImpl中執行sql語句。
源碼如下:
- public Object queryForObject(final String statementName,final Object parameterObject)
- throws DataAccessException {
- return execute(new SqlMapClientCallback() {
- //這里的SqlMapExecutor對象類型為SqlMapSessionImpl
- public Object doInSqlMapClient(SqlMapExecutor executor)throws SQLException {
- return executor.queryForObject(statementName, parameterObject);
- }
- });
- }
4. SqlMapSessionImpl().queryForObject()的方法很簡單,直接交給代理對象SqlMapExecutorDelegate處理:
- public Object queryForObject(String id, Object paramObject)throws SQLException {
- return delegate.queryForObject(session, id, paramObject);
- }
5. SqlMapExecutorDelegate的queryForObject()方法代碼如下:
- public Object queryForObject(SessionScope session, String id, Object paramObject, Object resultObject)throws SQLException {
- Object object = null;
- MappedStatement ms = getMappedStatement(id); //MappedStatement對象集是上文中提及的初始化方法SqlMapClientFactoryBean.afterPropertiesSet()中,由配置文件構建而成
- Transaction trans = getTransaction(session); // 用於事務執行
- boolean autoStart = trans == null;
- try {
- trans = autoStartTransaction(session, autoStart, trans);
- RequestScope request = popRequest(session, ms); // 從RequestScope池中獲取該次sql執行中的上下文環境RequestScope
- try {
- object = ms.executeQueryForObject(request, trans, paramObject, resultObject); // 執行sql
- } finally {
- pushRequest(request); //歸還RequestScope
- }
- autoCommitTransaction(session, autoStart);
- } finally {
- autoEndTransaction(session, autoStart);
- }
- return object;
- }
6. MappedStatement攜帶了SQL語句和執行過程中的相關信息,MappedStatement.executeQueryForObject()方法部分源碼如下:
- public Object executeQueryForObject(RequestScope request, Transaction trans, Object parameterObject, Object resultObject)
- throws SQLException {
- try {
- Object object = null;
- DefaultRowHandler rowHandler = new DefaultRowHandler();
- executeQueryWithCallback(request, trans.getConnection(), parameterObject, resultObject, rowHandler, SqlExecutor.NO_SKIPPED_RESULTS, SqlExecutor.NO_MAXIMUM_RESULTS);//執行sql語句
- //結果處理,返回結果
- List list = rowHandler.getList();
- if (list.size() > 1) {
- throw new SQLException("Error: executeQueryForObject returned too many results.");
- } else if (list.size() >0) {
- object = list.get(0);
- }
- return object;
- }
- ....
- }
7. MappedStatement.executeQueryWithCallback()方法包含了參數值映射、sql准備和sql執行等關鍵過程,部分源碼如下:
- protected void executeQueryWithCallback(RequestScope request, Connection conn, Object parameterObject, Object resultObject, RowHandler rowHandler,int skipResults, int maxResults)
- throws SQLException {
- ...
- try {
- parameterObject = validateParameter(parameterObject); //驗證入參
- Sql sql = getSql(); //獲取SQL對象
- errorContext.setMoreInfo("Check the parameter map.");
- ParameterMap parameterMap = sql.getParameterMap(request, parameterObject);// 入參映射
- errorContext.setMoreInfo("Check the result map.");
- ResultMap resultMap = sql.getResultMap(request, parameterObject); //結果映射
- request.setResultMap(resultMap);
- request.setParameterMap(parameterMap);
- errorContext.setMoreInfo("Check the parameter map.");
- Object[] parameters = parameterMap.getParameterObjectValues(request, parameterObject); //獲取參數值
- errorContext.setMoreInfo("Check the SQL statement.");
- String sqlString = sql.getSql(request, parameterObject); //獲取拼裝后的sql語句
- errorContext.setActivity("executing mapped statement");
- errorContext.setMoreInfo("Check the SQL statement or the result map.");
- RowHandlerCallback callback = new RowHandlerCallback(resultMap, resultObject, rowHandler);
- sqlExecuteQuery(request, conn, sqlString, parameters, skipResults, maxResults, callback); //sql執行
- errorContext.setMoreInfo("Check the output parameters.");
- if (parameterObject != null) {
- postProcessParameterObject(request, parameterObject, parameters);
- }
- errorContext.reset();
- sql.cleanup(request);
- notifyListeners();
- ....
- }
8. 到了執行中最核心的一步,也是最后一步: MappedStatement.sqlExecuteQuery()方法,它負責sql的最后執行,內部調用了SqlExecutor.executeQuery()方法,部分源碼如下:
- public void executeQuery(RequestScope request, Connection conn, String sql, Object[] parameters,int skipResults, intmaxResults, RowHandlerCallback callback)throws SQLException {
- ...
- PreparedStatement ps = null;
- ResultSet rs = null;
- setupResultObjectFactory(request);
- try {
- errorContext.setMoreInfo("Check the SQL Statement (preparation failed).");
- Integer rsType = request.getStatement().getResultSetType();
- //初始化PreparedStatement,設置sql、參數值等
- if (rsType != null) {
- ps = prepareStatement(request.getSession(), conn, sql, rsType);
- } else {
- ps = prepareStatement(request.getSession(), conn, sql);
- }
- setStatementTimeout(request.getStatement(), ps);
- Integer fetchSize = request.getStatement().getFetchSize();
- if (fetchSize != null) {
- ps.setFetchSize(fetchSize.intValue());
- }
- errorContext.setMoreInfo("Check the parameters (set parameters failed).");
- request.getParameterMap().setParameters(request, ps, parameters);
- errorContext.setMoreInfo("Check the statement (query failed).");
- ps.execute(); //執行
- errorContext.setMoreInfo("Check the results (failed to retrieve results).");
- // ResultSet處理
- rs = handleMultipleResults(ps, request, skipResults, maxResults, callback);
- } finally {
- try {
- closeResultSet(rs);
- } finally {
- closeStatement(request.getSession(), ps);
- }
- }
- }
上面這段代碼大家會非常熟悉,和本文開始處的代碼很相似,ibatis歸根到底,是對JDBC操作一定程度上的封裝而已。
下面在總體上概括sql的一般執行過程:
SqlMapClientImpl接到請求后,創建SqlMapSessionImpl對象(ThreadLocal,保證線程安 全),SqlMapSessionImpl交由內部的代理類SqlMapExecutorDelegate執行,代理類獲取相應的MappedStatement,交由MappedStatement對象執行,MappedStatement交由SqlExecutor執行,最終使 用JDBC方式執行sql。
小結
ibatis源碼規模較小,整體設計思路清晰,閱讀ibatis源碼可以按以下思路進行:
1. 了解ibatis框架的整體目標,用於解決哪些問題。
2. ibatis如何解決這些問題,帶着問題去學習。
3. 了解ibatis框架的核心接口和整體設計思路。
4. 抓住ibatis核心流程: 初始化和請求處理流程。
5. 詳細ibatis框架的關鍵細節實現,如ibatis中的配置文件解析,參數和結果映射等。