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

[b][size=large]背景[/size][/b]
介绍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执行->处理结果->关闭连接。

[b][size=large]问题[/size][/b]
上面的代码中包含了很多不稳定的因素,可能会经常发生修改:
1. 数据源和事务管理等
2. sql语句
3. 入参和结果处理
如果这些发生变化,我们需要直接修改代码,重新编译、打包、发布等。

[b][size=large]DIY[/size][/b]
[size=small]下面从我们自己DIY的视角来考虑如何设计框架,应对这些问题。框架的核心思想是抽取共性,封装可能出现的变化。
[i]如何处理数据源变化?[/i]
将数据源连接等过程固定,将数据源中易变的信息封装在一起(如driverClassName, url等),放在配置文件中。

[i]如何处理sql语句的变化?[/i]
将sql语句统一放在配置文件中,为每条sql语句设置标识,在代码中使用标识进行调用。

[i]如何应对入参和结果处理的变化?[/i]
将参数传递,结果映射到java bean等统一封装在配置文件中。

[color=red]结论: 将不变的流程固化到代码中,将变化的信息封装在配置文件中。[/color]
[/size]

[b][size=large]核心接口[/size][/b]
ibatis抽取了以下几个重要接口:

[b]1. SqlMapExecutor[/b]
该接口是对SQL操作行为的抽象,提供了SQL单条执行和批处理涉及的所有操作方法。
[img]http://dl.iteye.com/upload/attachment/0064/5302/80d4ec7d-a9a5-32d8-bbf9-f2268f1f5399.jpg[/img]

[b]2. SqlMapTransactionManager[/b]
该接口是对事务行为的抽象,提供了事务执行过程中的涉及的所有方法。
[img]http://dl.iteye.com/upload/attachment/0064/5304/94d57734-a38e-3ca0-acda-425604fb91f1.jpg[/img]

[b]3. SqlMapClient[/b]
该接口定位是SQL执行客户端,是线程安全的,用于处理多个线程的sql执行。它继承了上面两个接口,这意味着该接口具有SQL执行、批处理和事务处理的能力,如果你拥有该接口的实现类,意味着执行任何SQL语句对你来说是小菜一碟。该接口的核心实现类是SqlMapClientImpl。

[b]4. SqlMapSession[/b]
该接口在继承关系上和SqlMapClient一致,但它的定位是保存单线程sql执行过程的session信息。该接口的核心实现类是SqlMapSessionImpl。

[b]5. MappedStatement[/b]
该接口定位是单条SQL执行时的上下文环境信息,如SQL标识、SQL、参数信息、返回结果、操作行为等。

[b]6. ParameterMap/ResultMap[/b]
该接口用于在SQL执行的前后提供参数准备和执行结果集的处理。

以上接口的类图关系如下(部分):
[img]http://dl.iteye.com/upload/attachment/0064/5306/4642f482-60c3-3b95-9960-e6b3e6c3ad11.jpg[/img]

这里必须要强调SqlMapExecutorDelegate这个类,他是一个执行代理类,在ibatis框架中地位非常重要,因为他耦合了用户端的操作行为和执行环境,他持有执行操作的所需要的所有数据,同时管理着执行操作依赖的环境。

[b][size=large]初始化过程[/size][/b]
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;    }

[b][size=large]SQL执行过程[/size][/b]
下面以一个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 = (dataSource instanceof 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()方法。 [color=red]
注意: 这里调用对象主体为SqlMapSessionImpl,表示在线程session中执行sql(session是ThreadLocal的),而不在负责整体协调的SqlMapClientImpl中执行sql语句。[/color]
源码如下:

 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, int maxResults, 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。

[b][size=large]小结[/size][/b]
ibatis源码规模较小,整体设计思路清晰,阅读ibatis源码可以按以下思路进行:
1. 了解ibatis框架的整体目标,用于解决哪些问题。
2. ibatis如何解决这些问题,带着问题去学习。
3. 了解ibatis框架的核心接口和整体设计思路。
4. 抓住ibatis核心流程: 初始化和请求处理流程。
5. 详细ibatis框架的关键细节实现,如ibatis中的配置文件解析,参数和结果映射等。

ibatis源码学习(一)整体设计和核心流程相关推荐

  1. gallery3d源码学习总结(一)——绘制流程drawFocusItems

    eoe·Android开发者门户 标题: gallery3d源码学习总结(一)--绘制流程drawFocusItems [打印本页] 作者: specialbrian    时间: 2010-10-2 ...

  2. 【Android 源码学习】系统架构和启动流程

    Android 源码学习 系统架构和启动流程 望舒课堂 学习记录整理.以及以下参考文章的整理汇总.便于我个人的学习记录. 感谢IngresGe,Gityuan的精彩文章.为我们这些初探android系 ...

  3. OpenFire源码学习之四:openfire的启动流程

    openfire启动 ServerStarter 启动流程图: 启动的总入口在ServerStarter的main方法中.通过上图首先它会先加载它所需要的jar文件.最后通过java反射机制将XMPP ...

  4. hibernate 并发获取session失败 空指针_高并发之|通过ThreadPoolExecutor类的源码深度解析线程池执行任务的核心流程...

    核心逻辑概述 ThreadPoolExecutor是Java线程池中最核心的类之一,它能够保证线程池按照正常的业务逻辑执行任务,并通过原子方式更新线程池每个阶段的状态. ThreadPoolExecu ...

  5. flume源码学习8-hdfs sink的具体写入流程

    上一篇说了HDFSEventSink的实现,这里根据hdfs sink的配置和调用分析来看下sink中整个hdfs数据写入的过程: 线上hdfs sink的几个重要设置 1 2 3 4 5 6 7 8 ...

  6. Mybatis源码学习(三)SqlSession详解

    前言 上一章节我们学习了SqlSessionFactory的源码,SqlSessionFactory中的方法都是围绕着SqlSession来的.,那么SqlSession又是什么东东呢?这一章节我们就 ...

  7. RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的?

    RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的? 文章目录 RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的? 前言 项目 ...

  8. 以太坊源码学习(一) 正本清源

    以太坊源码学习(一)正本清源 背景 geth源码一直在不断增加,优化,发展到现在已经非常庞大,第一次看geth源码,会有不小的难度.虽然如此,还是可以从geth仓库的第一个commit开始,这时的代码 ...

  9. 开源中国源码学习UI篇(二)之NavigationDrawer+Fragment的使用分析

    前文链接:开源中国源码学习UI篇(一)之FragmentTabHost的使用分析 开源中国2.2版,完整源码地址为:http://git.oschina.net/oschina/android-app ...

最新文章

  1. Python 索引for循环
  2. ajax请求目标地址,AJAX功能目标
  3. JAVA生成并导出json文件
  4. springboot-Initializer例子及分析
  5. 三星电子第二季芯片需求大增 但手机销售疲软
  6. centos查看文件修改历史_Linux环境下查看历史操作命令及清除方法
  7. 用Google XML Sitemaps为你的网站创建Sitemap
  8. 深入浅出设计模式(十四):23种设计模式概念总结
  9. bdd java_二元判断图BDD及其JAVA实现的应用与研究
  10. 图片轮播html原生代码,原生js实现轮播图的示例代码
  11. Laravel文档梳理10、请求生命的周期
  12. 【OpenFOAM学前预备3——安装OpenFOAM-v8】
  13. java 针对专业技能可能会被问到的面试题
  14. 用docker跑机器学习环境
  15. QT作为设备接入阿里云平台
  16. php输出次方,php如何实现数值的整数次方(代码实例)
  17. nalu格式annex-B和avcc
  18. 关于解决'\u'开头的字符串转中文的方法
  19. 软件框架-无绪开发4
  20. Spring Boot 和 Spring 有什么区别

热门文章

  1. Vivado中的TCL脚本语言
  2. C#中Any CPU和X86和X64平台的差异对比
  3. 爱普生Epson L3158 一体机驱动
  4. Microsoft edge的下载速度怎么这么慢?如何提高下载速度? 启用 Chrome 或 Edge 浏览器自带的多线程下载功能
  5. Java使用多线程和GUI实现购买火车票<集合>
  6. 如何查看手机蓝牙的HFP的版本
  7. 关于化工行业如何报警
  8. 华为云面试指南—FusionAccess
  9. 【QQ群讨论精华】关于Move,Copy与Referrence
  10. 2009年顶级杀毒软件排名榜