`
learnworld
  • 浏览: 169558 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

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

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

背景
介绍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;
	}


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中的配置文件解析,参数和结果映射等。
  • 大小: 37.6 KB
  • 大小: 28.1 KB
  • 大小: 46.3 KB
分享到:
评论
5 楼 lishankang 2015-03-26  
好文章, NB
4 楼 zhangwei_david 2015-01-13  
   
3 楼 lf_can 2013-04-29  
写的蛮好的,不过有个小问题啊,SqlMapSessionImpl 本生的执行方法使用了 threadLocal 模式。但是使用Spring 来驱动Ibatis 后就没使用threadLocal 了。

---  SqlMapClientTemplate 中代码
SqlMapSession session = this.sqlMapClient.openSession(); //获取session信息 

---- SqlMapClientImpl 中 openSession 的实现为:
  public SqlMapSession openSession() {
    SqlMapSessionImpl sqlMapSession = new SqlMapSessionImpl(this);
    sqlMapSession.open();
    return sqlMapSession;
  }

大侠,不知道是不是这样的
2 楼 sweat89 2012-12-22  
好文! 
1 楼 yangsirjiayou 2012-03-21  
刚接触ibatis,还没有完全理解透彻,但是大侠剖析的很详细,以后继续学习!

相关推荐

    ibatis源码,ibatis源码 ibatis源码 ibatis源码

    本文将通过分析iBatis的源码,深入探讨其设计理念和实现机制。 一、iBatis架构概述 iBatis主要由SqlMapConfig.xml配置文件、SqlMap接口和Executor执行器三大部分构成。SqlMapConfig.xml配置文件定义了数据源、事务...

    ibatis源码

    描述中的"ibatis框架源码剖析书中附带的光盘,ibatis源码分析"暗示这可能是一个学习资源,用于深入理解iBATIS的工作原理,可能包括了对源码的详细解读和分析。 **iBATIS核心知识点** 1. **SQL映射**:iBATIS的核心...

    最新ibatis 源码

    ibatis源码 学习参考 对于学习ibatis很有帮助

    iBATIS框架源码剖析源码

    通过深入分析iBATIS的源码,开发者不仅可以了解其工作原理,还能学习到设计模式、数据库访问的最佳实践以及如何优雅地处理数据库操作。对于提升Java开发者的技能和理解数据库访问层的实现有极大的帮助。在实际开发中...

    ibatis源码+api文档+jar包

    3. 学习设计模式:Ibatis的源码中应用了许多经典的设计模式,如工厂模式、单例模式、代理模式等,这对于提升编程技能非常有帮助。 最后,API文档是开发者的重要参考材料,它详细解释了每个类、接口和方法的功能、...

    ibatis框架源码剖析光盘资料

    总的来说,ibatis框架源码的学习不仅可以帮助我们理解其工作原理,提升开发效率,还能为我们提供一种思考问题的角度,理解数据访问层的设计模式。通过对源码的深入剖析,我们可以更好地解决实际项目中的问题,进行...

    iBATIS框架源码剖析

    iBATIS框架源码剖析

    springMVC+ibatis的源码

    通过学习和分析这个源码,开发者不仅可以深入了解SpringMVC和iBatis的协同工作原理,还可以掌握如何在Eclipse这样的IDE中配置和运行这样的项目。这有助于提升对MVC模式的理解,提高数据库操作的能力,以及熟练运用...

    iBatis框架源码剖析

    iBATIS一词来源于“internet”和“abatis”的组合,是一个由Clinton Begin在2001年发起的开放源代码项目。于2010年6月16号被谷歌托管,改名为MyBatis。是一个基于SQL映射支持Java和·NET的持久层框架。

    iBATIS2.3.4 jar包及源码

    通常,将源码放在`lib`目录下并不常见,因为源码主要用于开发和学习,而不是运行时环境。 在iBATIS中,关键概念有: 1. SQL映射文件:这是XML格式的文件,用于定义SQL语句和结果映射。通过这些文件,开发者可以...

    ibatis2.3源码

    通过深入研究iBATIS 2.3的源码,开发者不仅可以了解其实现细节,还可以学习到如何设计一个高效的持久层框架,提升自己的编程技巧和设计能力。同时,这也为那些希望在现有基础上定制或优化iBATIS功能的开发者提供了...

    ibatis学习资料汇总

    iBatis 2.x版本是iBatis历史上的一个里程碑,其中包含了许多基础库和核心组件。这个jar包包含了以下主要部分: - iBatis核心库:提供SQL映射和数据访问的功能。 - SQLMapClient:用于创建和管理SQLSession对象。 - ...

    ibatis 学习源码

    PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN" "http://www.ibatis.com/dtd/sql-map-config-2.dtd"&gt; cacheModelsEnabled="true" enhancementEnabled="true" lazyLoadingEnabled="true" ...

    ibatisDemo 入门源码

    《IbatisDemo入门源码详解》 IbatisDemo是一个典型的基于Ibatis框架的入门示例,它为我们展示了如何在Java项目中使用Ibatis进行数据库操作。Ibatis,一个优秀的持久层框架,它允许开发者将SQL语句直接写在配置文件...

    ibatis源码及实例

    iBATIS一词来源于“internet”和“abatis”的组合,是一个由Clinton Begin在2001年发起的开放源代码项目。最初侧重于密码软件的开发,现在是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data ...

    iBATIS框架源码剖析pdf第二部分

    iBATIS是一个开源的...总之,"iBATIS框架源码剖析pdf第二部分"将带你深入iBATIS的内部,揭示其设计思想和实现细节。通过学习,你不仅可以提升对iBATIS的理解,还能为使用或开发类似的数据库访问框架打下坚实的基础。

    IBatis学习

    IBatis3.0学习IBatis3.0学习IBatis3.0学习IBatis3.0学习IBatis3.0学习IBatis3.0学习IBatis3.0学习

Global site tag (gtag.js) - Google Analytics