• ibatis源码学习(一)整体设计和核心流程


    本文主要从ibatis框架的基本代码骨架进行切入,理解ibatis框架的整体设计思路,各组件的实现细节将在后文进行分析。 

    背景 
    介绍ibatis实现之前,先来看一段jdbc代码: 
    Java代码  收藏代码
    1. Class.forName("com.mysql.jdbc.Driver");  
    2. String url = "jdbc:mysql://localhost:3306/learnworld";  
    3. Connection con = DriverManager.getConnection(url, "root","learnworld");           
    4. String sql = "select * from test";  
    5. PreparedStatement ps = con.prepareStatement(sql);             
    6. ResultSet rs = ps.executeQuery();  
    7. while(rs.next()){  
    8.     System.out.println("id=" + rs.getInt(1)+". score=" + rs.getInt(2));  
    9. }  

    上面这段代码大家比较熟悉,这是一个典型的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配置: 
    Java代码  收藏代码
    1. <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">  
    2.     <property name="dataSource" ref="dataSource" />  
    3.     <property name="configLocation" value="classpath/sqlmap/sqlmap-ibatis.xml" />  
    4. </bean>  

    下面看一下SqlMapClientFactoryBean的初始化方法afterPropertiesSet(),用于构建sqlMapClient对象: 
    Java代码  收藏代码
    1. public void afterPropertiesSet() throws Exception {  
    2.     ...  
    3.   
    4.     this.sqlMapClient = buildSqlMapClient(this.configLocations, this.mappingLocations, this.sqlMapClientProperties);   //初始化核心方法,构建sqlMapClient对象  
    5.   
    6.     ...  
    7. }  

    看一下buildSqlMapClient()的实现: 
    Java代码  收藏代码
    1. protected SqlMapClient buildSqlMapClient(  
    2.         Resource[] configLocations, Resource[] mappingLocations, Properties properties)  
    3.         throws IOException {  
    4.                ...  
    5.     SqlMapClient client = null;  
    6.     SqlMapConfigParser configParser = new SqlMapConfigParser();  
    7.     for (int i = 0; i < configLocations.length; i++) {  
    8.         InputStream is = configLocations[i].getInputStream();  
    9.         try {  
    10.             client = configParser.parse(is, properties); //通过SqlMapConfigParser解析配置文件,生成SQLMapClientImpl对象  
    11.         }  
    12.         catch (RuntimeException ex) {  
    13.             throw new NestedIOException("Failed to parse config resource: " + configLocations[i], ex.getCause());  
    14.         }  
    15.     }  
    16.     ...  
    17.     return client;  
    18. }  


    SQL执行过程 
    下面以一个select语句的执行过程,分析一下以上各ibatis核心接口相互如何配合。 
    1. dao调用SqlMapClientTemplate的query()方法: 
    Java代码  收藏代码
    1. public Object queryForObject(final String statementName, final Object parameterObject)  
    2.         throws DataAccessException {  
    3.   
    4.     return execute(new SqlMapClientCallback() {  
    5.         public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {  
    6.             return executor.queryForObject(statementName, parameterObject);  
    7.         }  
    8.     });  
    9. }  


    2. execute()方法中展示了执行过程中的核心逻辑:获取session -> 获取connection -> 执行sql ->释放connection -> 关闭session。 
    部分源码如下: 
    Java代码  收藏代码
    1. public Object execute(SqlMapClientCallback action) throws DataAccessException {  
    2.                 ...  
    3.         SqlMapSession session = this.sqlMapClient.openSession(); //获取session信息  
    4.           
    5.         Connection ibatisCon = null;     //获取Connection  
    6.   
    7.         try {  
    8.             Connection springCon = null;  
    9.             DataSource dataSource = getDataSource();  
    10.             boolean transactionAware = (dataSource instanceof TransactionAwareDataSourceProxy);  
    11.   
    12.             try {  
    13.                 ibatisCon = session.getCurrentConnection();  
    14.                 if (ibatisCon == null) {  
    15.                     springCon = (transactionAware ?  
    16.                             dataSource.getConnection() : DataSourceUtils.doGetConnection(dataSource));  
    17.                     session.setUserConnection(springCon);  
    18.                 ...  
    19.                 }  
    20.             }  
    21.             ...  
    22.   
    23.             // Execute given callback...  
    24.             try {  
    25.                 return action.doInSqlMapClient(session); //执行SQL  
    26.             }  
    27.             ...  
    28.             finally {  
    29.                         // 关闭Connection  
    30.                 try {  
    31.                     if (springCon != null) {  
    32.                         if (transactionAware) {  
    33.                             springCon.close();   
    34.                         }  
    35.                         else {  
    36.                             DataSourceUtils.doReleaseConnection(springCon, dataSource);  
    37.                         }  
    38.                     }  
    39.                 }  
    40.                   
    41.             if (ibatisCon == null) {  
    42.                 session.close(); //关闭session  
    43.             }  
    44.         }  


    3. action.doInSqlMapClient(session)调用SqlMapSessionImpl().queryForObject()方法。 
    注意: 这里调用对象主体为SqlMapSessionImpl,表示在线程session中执行sql(session是ThreadLocal的),而不在负责整体协调的SqlMapClientImpl中执行sql语句。
     
    源码如下: 
    Java代码  收藏代码
    1. public Object queryForObject(final String statementName, final Object parameterObject)  
    2.         throws DataAccessException {  
    3.   
    4.     return execute(new SqlMapClientCallback() {  
    5.        //这里的SqlMapExecutor对象类型为SqlMapSessionImpl  
    6.         public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {  
    7.             return executor.queryForObject(statementName, parameterObject);  
    8.         }  
    9.     });  
    10. }  


    4. SqlMapSessionImpl().queryForObject()的方法很简单,直接交给代理对象SqlMapExecutorDelegate处理: 
    Java代码  收藏代码
    1. public Object queryForObject(String id, Object paramObject) throws SQLException {  
    2.   return delegate.queryForObject(session, id, paramObject);  
    3. }  


    5. SqlMapExecutorDelegate的queryForObject()方法代码如下: 
    Java代码  收藏代码
    1. public Object queryForObject(SessionScope session, String id, Object paramObject, Object resultObject) throws SQLException {  
    2.   Object object = null;  
    3.   
    4.   MappedStatement ms = getMappedStatement(id); //MappedStatement对象集是上文中提及的初始化方法SqlMapClientFactoryBean.afterPropertiesSet()中,由配置文件构建而成  
    5.   Transaction trans = getTransaction(session); // 用于事务执行  
    6.   boolean autoStart = trans == null;  
    7.   
    8.   try {  
    9.     trans = autoStartTransaction(session, autoStart, trans);  
    10.   
    11.     RequestScope request = popRequest(session, ms);  // 从RequestScope池中获取该次sql执行中的上下文环境RequestScope   
    12.     try {  
    13.       object = ms.executeQueryForObject(request, trans, paramObject, resultObject);   // 执行sql  
    14.     } finally {  
    15.       pushRequest(request);  //归还RequestScope  
    16.     }  
    17.   
    18.     autoCommitTransaction(session, autoStart);  
    19.   } finally {  
    20.     autoEndTransaction(session, autoStart);  
    21.   }  
    22.   
    23.   return object;  
    24. }  


    6. MappedStatement携带了SQL语句和执行过程中的相关信息,MappedStatement.executeQueryForObject()方法部分源码如下: 
    Java代码  收藏代码
    1. public Object executeQueryForObject(RequestScope request, Transaction trans, Object parameterObject, Object resultObject)  
    2.     throws SQLException {  
    3.   try {  
    4.     Object object = null;  
    5.   
    6.     DefaultRowHandler rowHandler = new DefaultRowHandler();  
    7.     executeQueryWithCallback(request, trans.getConnection(), parameterObject, resultObject, rowHandler, SqlExecutor.NO_SKIPPED_RESULTS, SqlExecutor.NO_MAXIMUM_RESULTS); //执行sql语句  
    8.       
    9.     //结果处理,返回结果  
    10.     List list = rowHandler.getList();   
    11.     if (list.size() > 1) {  
    12.       throw new SQLException("Error: executeQueryForObject returned too many results.");  
    13.     } else if (list.size() > 0) {  
    14.       object = list.get(0);  
    15.     }  
    16.   
    17.     return object;  
    18.   }   
    19.   ....  
    20. }  


    7. MappedStatement.executeQueryWithCallback()方法包含了参数值映射、sql准备和sql执行等关键过程,部分源码如下: 
    Java代码  收藏代码
    1. protected void executeQueryWithCallback(RequestScope request, Connection conn, Object parameterObject, Object resultObject, RowHandler rowHandler, int skipResults, int maxResults)  
    2.     throws SQLException {  
    3.     ...  
    4.   try {  
    5.     parameterObject = validateParameter(parameterObject);  //验证入参  
    6.   
    7.     Sql sql = getSql();  //获取SQL对象  
    8.   
    9.     errorContext.setMoreInfo("Check the parameter map.");  
    10.     ParameterMap parameterMap = sql.getParameterMap(request, parameterObject);// 入参映射  
    11.   
    12.     errorContext.setMoreInfo("Check the result map.");  
    13.     ResultMap resultMap = sql.getResultMap(request, parameterObject); //结果映射  
    14.   
    15.     request.setResultMap(resultMap);  
    16.     request.setParameterMap(parameterMap);  
    17.   
    18.     errorContext.setMoreInfo("Check the parameter map.");  
    19.     Object[] parameters = parameterMap.getParameterObjectValues(request, parameterObject);  //获取参数值  
    20.   
    21.     errorContext.setMoreInfo("Check the SQL statement.");  
    22.     String sqlString = sql.getSql(request, parameterObject);  //获取拼装后的sql语句  
    23.   
    24.     errorContext.setActivity("executing mapped statement");  
    25.     errorContext.setMoreInfo("Check the SQL statement or the result map.");  
    26.     RowHandlerCallback callback = new RowHandlerCallback(resultMap, resultObject, rowHandler);  
    27.     sqlExecuteQuery(request, conn, sqlString, parameters, skipResults, maxResults, callback);  //sql执行  
    28.   
    29.     errorContext.setMoreInfo("Check the output parameters.");  
    30.     if (parameterObject != null) {  
    31.       postProcessParameterObject(request, parameterObject, parameters);  
    32.     }  
    33.   
    34.     errorContext.reset();  
    35.     sql.cleanup(request);  
    36.     notifyListeners();  
    37.     ....  
    38. }  


    8. 到了执行中最核心的一步,也是最后一步: MappedStatement.sqlExecuteQuery()方法,它负责sql的最后执行,内部调用了SqlExecutor.executeQuery()方法,部分源码如下: 
    Java代码  收藏代码
    1. public void executeQuery(RequestScope request, Connection conn, String sql, Object[] parameters, int skipResults, int maxResults, RowHandlerCallback callback) throws SQLException {  
    2.   ...  
    3.   PreparedStatement ps = null;  
    4.   ResultSet rs = null;  
    5.   setupResultObjectFactory(request);  
    6.   try {  
    7.     errorContext.setMoreInfo("Check the SQL Statement (preparation failed).");  
    8.     Integer rsType = request.getStatement().getResultSetType();  
    9.     //初始化PreparedStatement,设置sql、参数值等  
    10.     if (rsType != null) {  
    11.       ps = prepareStatement(request.getSession(), conn, sql, rsType);  
    12.     } else {  
    13.       ps = prepareStatement(request.getSession(), conn, sql);  
    14.     }  
    15.     setStatementTimeout(request.getStatement(), ps);  
    16.     Integer fetchSize = request.getStatement().getFetchSize();  
    17.     if (fetchSize != null) {  
    18.       ps.setFetchSize(fetchSize.intValue());  
    19.     }  
    20.     errorContext.setMoreInfo("Check the parameters (set parameters failed).");  
    21.     request.getParameterMap().setParameters(request, ps, parameters);  
    22.     errorContext.setMoreInfo("Check the statement (query failed).");  
    23.     ps.execute(); //执行  
    24.     errorContext.setMoreInfo("Check the results (failed to retrieve results).");  
    25.   
    26.     // ResultSet处理  
    27.     rs = handleMultipleResults(ps, request, skipResults, maxResults, callback);  
    28.   } finally {  
    29.     try {  
    30.       closeResultSet(rs);  
    31.     } finally {  
    32.       closeStatement(request.getSession(), ps);  
    33.     }  
    34.   }  
    35.   
    36. }  

    上面这段代码大家会非常熟悉,和本文开始处的代码很相似,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中的配置文件解析,参数和结果映射等。
  • 相关阅读:
    内存管理——显式类型转换
    内存管理——隐式类型转换
    数据类型——构造类型——枚举
    构造类型——联合类型
    结构体学习笔记9——结构体大小计算规则
    结构体学习笔记8——内存对齐
    结构体学习笔记7——结构体数组
    结构体学习笔记6——结构体嵌套
    结构体学习笔记5——指针成员与函数成员
    一.js高级(9)深拷贝-浅拷贝的其他方法
  • 原文地址:https://www.cnblogs.com/daichangya/p/12958780.html
Copyright © 2020-2023  润新知