[转帖]ibatis源码学习——整体设计和核心流程_Tomcat, WebLogic及J2EE讨论区_Weblogic技术|Tuxedo技术|中间件技术|Oracle论坛|JAVA论坛|Linux/Unix技术|hadoop论坛_联动北方技术论坛  
网站首页 | 关于我们 | 服务中心 | 经验交流 | 公司荣誉 | 成功案例 | 合作伙伴 | 联系我们 |
联动北方-国内领先的云技术服务提供商
»  游客             当前位置:  论坛首页 »  自由讨论区 »  Tomcat, WebLogic及J2EE讨论区 »
总帖数
1
每页帖数
101/1页1
返回列表
0
发起投票  发起投票 发新帖子
查看: 3738 | 回复: 0   主题: [转帖]ibatis源码学习——整体设计和核心流程        下一篇 
    本主题由 yhl.admin 于 2012-12-28 16:34:53 版块置顶
haili.yang
注册用户
等级:少校
经验:936
发帖:71
精华:1
注册:2012-12-24
状态:离线
发送短消息息给haili.yang 加好友    发送短消息息给haili.yang 发消息
发表于: IP:您无权察看 2012-12-28 15:32:03 | [全部帖] [楼主帖] 楼主

本文主要从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)); 



上面这段代码大家比较熟悉,这是一个典型的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.        ... 



看一下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; 



初始化的核心是通过配置文件构建SQLMapClientImpl对象和其内部的各个属性,这里就不详述了,详细构建过程将另文分析。 
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.        }); 



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.        }); 



4. SqlMapSessionImpl().queryForObject()的方法很简单,直接交给代理对象SqlMapExecutorDelegate处理: 

Java代码   北京联动北方科技有限公司


  1. public Object queryForObject(String id, Object paramObject) throws SQLException { 
  2.        return delegate.queryForObject(session, id, paramObject); 



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.  return object; 



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.        .... 



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.  } 



上面这段代码大家会非常熟悉,和本文开始处的代码很相似,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中的配置文件解析,参数和结果映射等。


--------------------版主评价------------------------
yhl.admin  2012-12-28 16:19:09
不错哦...


赞(0)    操作        顶端 
总帖数
1
每页帖数
101/1页1
返回列表
发新帖子
请输入验证码: 点击刷新验证码
您需要登录后才可以回帖 登录 | 注册
技术讨论