`
Yinny
  • 浏览: 292903 次
  • 性别: Icon_minigender_2
  • 来自: 杭州
社区版块
存档分类
最新评论

iBATIS 对 SQL 语句的解析过程

阅读更多
总体来说 iBATIS 的系统结构还是比较简单的,它主要完成两件事情:
根据 JDBC 规范建立与数据库的连接;
通过反射打通 Java 对象与数据库参数交互之间相互转化关系。
iBATIS 的框架结构也是按照这种思想来组织类层次结构的,其实它是一种典型的交互式框架。先期准备好交互的必要条件,然后构建一个交互的环境,交互环境中还划分成会话,每次的会话也有一个环境。当这些环境都准备好了以后,剩下的就是交换数据了。其实涉及到网络通信,一般都会是类似的处理方式。
下图是 iBATIS 框架的主要的类层次结构图


上面的类图中左边 SqlMapClient 接口主要定义了客户端的操作行为包括 select、insert、update、delete。而右边主要是定义了当前客户端在当前线程的执行环境。SqlMapSession 可以共享使用,也可以自己创建,如果是自己创建在结束时必须要调用关闭接口关闭。
当使用者持有了 SqlMapClientImpl 对象就可以使用 iBATIS 来工作了。这里还要提到另外一个类 SqlMapExecutorDelegate 这个类从名字就可以看出他是执行代理类。这个类非常重要,重要是因为他耦合了用户端的执行操作行为和执行的环境,他持有执行操作的所需要的数据,同时提供管理着执行操作依赖的环境。所以他是一个强耦合的类,也可以看做是个工具类。

核心接口
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框架中地位非常重要,因为他耦合了用户端的操作行为和执行环境,他持有执行操作的所需要的所有数据,同时管理着执行操作依赖的环境。

下面是一个Spring中sqlMapClient的bean配置:

	<bean id="group1SqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
		<property name="configLocation">
			<value>sql-map.xml</value>
		</property>
		<property name="dataSource">
			<ref local="group1"/>
		</property>
	</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;  
}  


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 = (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()方法。
注意: 这里调用对象主体为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, 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。

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

相关推荐

    根据MyBatis或iBatis的SQLMapper文件反向生成数据库表

    根据MyBatis或iBatis的SQLMapper文件解析生成数据库表,通常是指通过解析MyBatis或iBatis的SQLMapper文件中的SQL语句,然后根据这些SQL语句来生成对应的数据库表结构。这样的需求可能源于需要将已有的SQLMapper文件...

    ideal mybatis打印sql插件

    把 mybatis 输出的sql日志还原成完整的sql语句。 将日志输出的sql语句中的问号 ? 替换成真正的参数值。 通过 "Tools -&gt; MyBatis Log Plugin" 菜单或快捷键 "Ctrl+Shift+Alt+O" 启用。 点击窗口左边的 "Filter" ...

    Spring数据库访问之iBatis

    相对于Hibernate等ORM框架的全自动SQL,那么iBatis则属于半自动化的ORM框架,我们需要编写SQL语句,由iBatis进行数据库访问,返回结果。而iBatis可以为我们做的更多,比如对查询结果的封装等等。虽然不如全自动SQL...

    mybatis-3-mybatis-3.5.13.zip

    MyBatis作为一个流行的持久化框架,其源码是开放的,允许开发者深入了解框架...MyBatis使用XML解析器解析映射文件,将SQL语句和数据库操作转化为Java对象。 4. SQL执行: 当执行SQL操作时,MyBatis会将映射文件。。。

    mybatis 模糊查询的实现方法

    mybatis的逆向助手确实好用,可以省去很多编写常规sql语句的时间,但是它没办法自动生成模糊查询语句,但开发中模糊查询是必不可少的,所以,需要手动对mapper编写模糊查询功能。 这里先明确MyBatis/Ibatis中#和$的...

    AppFramework_V1.0

    &lt;br&gt;IBatisNet是一个轻量级ORMap工具,它把所有的SQL脚本以模板的方式集中到若干个XML配置文件里,用反射的方式向把C#类实体对象属性与SQL模板的参数绑定,动态生成参数化的SQL语句发送给数据库执行,查询的结果...

    AppFramework_V1.0_New

    &lt;br&gt;IBatisNet是一个轻量级ORMap工具,它把所有的SQL脚本以模板的方式集中到若干个XML配置文件里,用反射的方式向把C#类实体对象属性与SQL模板的参数绑定,动态生成参数化的SQL语句发送给数据库执行,查询的结果...

    halo-dal:java 分布式数据库访问框架,可以结合任何使用PreparedStatement操作的框架。在java jdbc api层实现 分表分库 路由解析的 框架 可以单独或者与用hibernate ibatis spring-jdbc 等框架结合使用,屏蔽api层使用差异,能实现 jdbc 单数据库事务,目的是为了方便的进行分表分库程序的开发

    谢谢大家的关注#halo-dal使用说明#####使用场景:数据库分布式访问#####使用语言:java#####使用条件:支持PreparedStatement处理的任何jdbc框架,最好配合spring管理数据库连接池.#####sql语句必须使用小写字符#####jdk...

    spring chm文档

    11.2.6. 执行SQL语句 11.2.7. 执行查询 11.2.8. 更新数据库 11.3. 控制数据库连接 11.3.1. DataSourceUtils类 11.3.2. SmartDataSource接口 11.3.3. AbstractDataSource类 11.3.4. ...

    AppFramework数据库访问组件_代码生成插件_V1.1.rar

    &lt;br&gt;IBatisNet是一个轻量级ORMap工具,它把所有的SQL脚本以模板的方式集中到若干个XML配置文件里,用反射的方式向把C#类实体对象属性与SQL模板的参数绑定,动态生成参数化的SQL语句发送给数据库执行,查询的结果...

    Spring 2.0 开发参考手册

    11.2.6. 执行SQL语句 11.2.7. 执行查询 11.2.8. 更新数据库 11.3. 控制数据库连接 11.3.1. DataSourceUtils类 11.3.2. SmartDataSource接口 11.3.3. AbstractDataSource类 11.3.4. ...

    Spring中文帮助文档

    11.2.6. 执行SQL语句 11.2.7. 执行查询 11.2.8. 更新数据库 11.2.9. 获取自动生成的主键 11.3. 控制数据库连接 11.3.1. DataSourceUtils类 11.3.2. SmartDataSource接口 11.3.3. AbstractDataSource类 ...

    Spring API

    11.2.6. 执行SQL语句 11.2.7. 执行查询 11.2.8. 更新数据库 11.2.9. 获取自动生成的主键 11.3. 控制数据库连接 11.3.1. DataSourceUtils类 11.3.2. SmartDataSource接口 11.3.3. AbstractDataSource类 ...

    Spring-Reference_zh_CN(Spring中文参考手册)

    11.2.6. 执行SQL语句 11.2.7. 执行查询 11.2.8. 更新数据库 11.3. 控制数据库连接 11.3.1. DataSourceUtils类 11.3.2. SmartDataSource接口 11.3.3. AbstractDataSource类 11.3.4. SingleConnectionDataSource类 ...

    Java面试宝典2010版

    8.用一条SQL语句 查询出每门课都大于80分的学生姓名 9.所有部门之间的比赛组合 10.每个月份的发生额都比101科目多的科目 11.统计每年每月的信息 12.显示文章标题,发帖人、最后回复时间 13.删除除了id号不同,...

    最新Java面试宝典pdf版

    8.用一条SQL语句 查询出每门课都大于80分的学生姓名 100 9.所有部门之间的比赛组合 100 10.每个月份的发生额都比101科目多的科目 101 11.统计每年每月的信息 102 12.显示文章标题,发帖人、最后回复时间 103 13.删除...

    Java面试笔试资料大全

    8.用一条SQL语句 查询出每门课都大于80分的学生姓名 100 9.所有部门之间的比赛组合 100 10.每个月份的发生额都比101科目多的科目 101 11.统计每年每月的信息 102 12.显示文章标题,发帖人、最后回复时间 103 13.删除...

    JAVA面试宝典2010

    8.用一条SQL语句 查询出每门课都大于80分的学生姓名 100 9.所有部门之间的比赛组合 100 10.每个月份的发生额都比101科目多的科目 101 11.统计每年每月的信息 102 12.显示文章标题,发帖人、最后回复时间 103 13.删除...

    Java面试宝典-经典

    8.用一条SQL语句 查询出每门课都大于80分的学生姓名 100 9.所有部门之间的比赛组合 100 10.每个月份的发生额都比101科目多的科目 101 11.统计每年每月的信息 102 12.显示文章标题,发帖人、最后回复时间 103 13.删除...

Global site tag (gtag.js) - Google Analytics