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

ibatis_2.3源码中insert&update&query垂直浅析

阅读更多

题记:

    废话不多,慢慢写吧

 

写的可能有点乱,但思路是按照insert操作来写的。

 

 

问题1:线程安全

    在整个app多线程中,可以共用一个SqlMapClient来执行操作。原理是利用了ThreadLocal,ThreadLocal实际上是不同的Thread以ThreadLocal自身对象为key的一份ThreadLocalMap拷贝,每个thread创建了各自的Map,这个Map的key就是ThreadLocal的hashcode,所以,同一个ThreadLocal存储的不同value会存储在Map中table数组的同一个index上,并组成了链表。不同的ThreadLocal则存储在Thread中不同的index处,这个Map也不可能大小总为1;

 

    所以为了用ThreadLocal,我想一般需要增加一个胶合层来做一些工作,看上去像门面模式。在ibatis中,这个类就是SqlMapClientImpl,如下:

 

public class SqlMapClientImpl implements ExtendedSqlMapClient {

  /**
   * Delegate for SQL execution
   */
  public SqlMapExecutorDelegate delegate;
  protected ThreadLocal localSqlMapSession = new ThreadLocal();

 protected SqlMapSessionImpl getLocalSqlMapSession() {
    SqlMapSessionImpl sqlMapSession = (SqlMapSessionImpl) localSqlMapSession.get();
    if (sqlMapSession == null || sqlMapSession.isClosed()) {
      sqlMapSession = new SqlMapSessionImpl(this);
      localSqlMapSession.set(sqlMapSession);
    }
    return sqlMapSession;
  }
...
}

 

 

 这里看到,真正做事的是SqlMapExecutorDelegate这个类,并且还注意到,这里其实把SqlMapExecutorDelegate这个实现类传递给了sqlMapSession,又封装了一层。

 

 

问题2:Session上下文(事务控制)

    不是很深刻理解SessionScope,但从SessionScope的成员变量上看,每个SessionScope管理自己的事务,在这个事务中执行过的所有sql语句。

    去看了看SqlMapSessionImpl ,并且集成了SqlMapExecutor接口,这里应该是代理模式,因为SqlMapExecutorDelegate和SqlMapSessionImpl的接口是“相同”,如下:

 

public class SqlMapSessionImpl implements SqlMapSession {

  protected SqlMapExecutorDelegate delegate;
  protected SessionScope session;
  protected boolean closed;

  /**
   * Constructor
   *
   * @param client - the client that will use the session
   */
  public SqlMapSessionImpl(ExtendedSqlMapClient client) {
    this.delegate = client.getDelegate();
    this.session = this.delegate.popSession();
    this.session.setSqlMapClient(client);
    this.session.setSqlMapExecutor(client);
    this.session.setSqlMapTxMgr(client);
    this.closed = false;
  }

 

 

 

多出了个SessionScope,这个SessionScope类似装饰模式,而SessionScope应该是上下文的主要工作类了。SessionScope被SqlMapExecutorDelegate中的一个pool维护着,到此,可以看到很多操作都包装在了SqlMapExecutorDelegate类里。

  SessionScope对象池用Collections.synchronizedList(new ArrayList(size));线程安全的List来存储,因为所有SessionScope都公用同一个SqlMapExecutorDelegate。

 

总结下关系是:SqlMapSessionImpl 封装了主要工作类SessionScope 和 接收 外部引用SqlMapExecutorDelegate,SessionScope正是由SqlMapExecutorDelegate的对象池来管理。SqlMapSessionImpl只是做了代理工作。

    目前我感觉不用SqlMapSessionImpl代理类,直接用delegate.popSession();来获得一个SessionScope也应该是可以的,为什么要这样设计呢?理由在哪里呢?

 

 

问题3:insert操作

    我们继续,看到每个线程把持着自己的SqlMapSessionImpl,并且在SqlMapSessionImpl初始化的时候从对象池中的List拿到了一个SessionScope,SessionScope里包含了Transaction,接着就可以进行数据库操作了就看SqlMapSessionImpl 的insert方法:

 

  public Object insert(String id, Object param) throws SQLException {
    return delegate.insert(session, id, param);
  }

 

 

 看到,insert操作回传给了公共的SqlMapExecutorDelegate,并传递了SessionScope来区分。

 

insert代码如下:

 

 public Object insert(SessionScope session, String id, Object param) throws SQLException {
    Object generatedKey = null;

    MappedStatement ms = getMappedStatement(id);
    Transaction trans = getTransaction(session);
    boolean autoStart = trans == null;

    try {
      trans = autoStartTransaction(session, autoStart, trans);

      SelectKeyStatement selectKeyStatement = null;
      if (ms instanceof InsertStatement) {
        selectKeyStatement = ((InsertStatement) ms).getSelectKeyStatement();
      }

      if (selectKeyStatement != null && !selectKeyStatement.isAfter()) {
        generatedKey = executeSelectKey(session, trans, ms, param);
      }

      RequestScope request = popRequest(session, ms);
      try {
        ms.executeUpdate(request, trans, param);
      } finally {
        pushRequest(request);
      }

      if (selectKeyStatement != null && selectKeyStatement.isAfter()) {
        generatedKey = executeSelectKey(session, trans, ms, param);
      }

      autoCommitTransaction(session, autoStart);
    } finally {
      autoEndTransaction(session, autoStart);
    }

    return generatedKey;
  }

 

 

     id是XML中定义SQL语句的名称,param是参数。用getMappedStatement方法,根据id来获取需要执行的MappedStatement,XML文件中所有定义的Statement都存储在一个叫mappedStatements的HashMap中,并以XML定义的名称(即Id)为key,MappedStatement对象为value,这样根据名称就取到了对应的MappedStatement。

    然后根据session来获取Transaction,如下:

 

 

public Transaction getTransaction(SessionScope session) { return session.getTransaction(); }

 

 

     很显然,在session初始化的时候就已经定义好了一个Transaction,但这个Transaction不一定有值。

 

    拿到Transaction后,判断是否自动提交事务,如果是的,则开始session中的事务,个人觉得一个好纠结的函数,希望有大大来解释下:

 

protected Transaction autoStartTransaction(SessionScope session, boolean autoStart, Transaction trans) throws SQLException {
    Transaction transaction = trans;
    if (autoStart) {
      session.getSqlMapTxMgr().startTransaction();
      transaction = getTransaction(session);
    }
    return transaction;
  }

 

 

 

   然后看到session.getSqlMapTxMgr().startTransaction();这个方法,根据之前的分析,SqlMapSessionImpl管理这一个SessionScope和一个SqlMapExecutorDelegate,而getSqlMapTxMgr()实际上指向了SqlMapSessionImpl,而SqlMapSessionImpl只是一个代理类,这时SqlMapSessionImpl的startTransaction()方法实际指向了SqlMapExecutorDelegate的startTransaction(Session)方法,因为SqlMapSessionImpl把持这SessionScope和SqlMapExecutorDelegate对象,所以很轻松把当前SqlMapSessionImpl中的SessionScope为参数调用SqlMapExecutorDelegate的startTransaction(Session)了,最后SqlMapExecutorDelegate调用自己的事务管理类TransactionManager把Session参数传进去,并做事务操作,startTransaction这时新建立一个Transaction对象,并把new出来的Transaction对象赋值给Session,且事务池+1,设定状态。

 

    总结一下:SqlMapSessionImpl作为了SessionScope和SqlMapExecutorDelegate中间沟通的桥梁。

 

    executeUpdate被封装在GeneralStatement对象里面,并其子类有DeleteStatement,SelectStatement,InsertStatement等,所有的SQL解析,执行,返回结果集都集中在这里。

 

    最后提交事务:autoCommitTransaction(session, autoStart);如果autoStart为true,则执行session中的commitTransaction()方法,类似startTransaction()一样实际调用SqlMapExecutorDelegate的commitTransaction()方法,利用TransactionManager类的public void commit(SessionScope session)方法提交事务,如下:

 

 public void commit(SessionScope session) throws SQLException, TransactionException {
    Transaction trans = session.getTransaction();
    TransactionState state = session.getTransactionState();
    if (state == TransactionState.STATE_USER_PROVIDED) {
       } else if (state != TransactionState.STATE_STARTED && state != TransactionState.STATE_COMMITTED ) {
      throw new TransactionException("TransactionManager could not commit.  No transaction is started.");
    }
    if (session.isCommitRequired() || forceCommit) {
      trans.commit();
      session.setCommitRequired(false);
    }
    session.setTransactionState(TransactionState.STATE_COMMITTED);
  }

 

 

    总结一下:所有的事务控制都转接给公共SqlMapExecutorDelegate类中的TransactionManager类操作,TransactionManager自己维护着一个事务池,事务的新增和销毁都在这里集中管理。

 

 

问题4:执行SQL语句

    之前分析到SQL语句的校验、执行和结果集的返回都封装在了XXXStatement类中,BaseStatement是个抽象类,也是个很重要的类,GeneralStatement是extend这个抽象类的基础类。

其中执行sql的语句为:

 

 

ms.executeUpdate(request, trans, param);

 

    request是从request对象池中取到的(request对象池?what's that?),trans只是用来传递Connection的,param是参数对象了。

 

    首先建立点关键日志信息:ErrorContext;

    然后校验param参数,通过GeneralStatement的validateParameter方法,不过我没看明白;

    然后通过Sql接口的getParameterMap方法取sql语句中映射的元素,返回ParameterMap;

    取返回的元素,通过getResultMap方法,和上面类似,返回ResultMap;所以可以看到ParameterMap和ResultMap这两组类是存储ibatis的映射的,进一步发现ParameterMap和ResultMap其实在初始化XML的时候就解析好了,并储存在这些BaseStatement里,一条sql语句一个BaseStatement对象,而这些BaseStatement对象缓存SqlMapExecutorDelegate里的一个HashMap里,要用的时候就拿出来,并非用时再临时创建。另外这里的Sql接口被设计成为支持动态sql和静态sql,接收的参数为RequestScopes(存储了ParameterMap和ResultMap)、parameterObject。

    然后开始取parameterObject的值(也就是param对象里的成员变量值),通过ParameterMap的getParameterObjectValues方法,ParameterMap内部则调用DataExchange的getData方法(不同的param,比如JavaBean,Map,List需要不同的取值方式),所以每个ParameterMap都指向一个自己特定的DataExchange方式,有ComplexDataExchange,DomDataExchange,JavaBeanDataExchange,ListDataExchange等,用来支持XML中指定不同的parameter。每个DataExchange处理方式当然就不一样了,但都返回一个Object []数组。

 

    然后对于动态sql,需要重新构造一个新的sql,调用String sqlString = sql.getSql(request, parameterObject);方法;

    然后开始执行sql,调用rows = sqlExecuteUpdate(request, trans.getConnection(), sqlString, parameters);实际上是调用SqlExecutor这个实现类来执行所有sql的。

      首先构造PreparedStatement,先从SessionScope中取是否有缓存,设置PreparedStatement超时时间,然后通过ParameterMap的setParameters方法将之前取的parameterObject值set到PreparedStatement里面去。

    最后执行execute,取到返回行数。

  最后清理下现场。其中有个executeListeners,没看太明白。

 

    总结一下:在ibatis初始化的时候,也就是解析XML配置文件的时候,就对XML中每一个sql语句创建了一个BaseStatement对象存储在SqlMapExecutorDelegate的一个HashMap里面,在创建BaseStatement的时候还额外创建了ParameterMap和ResultMap对象,ParameterMap是用来管理sql中的输入参数的,根据parameterClass的不同,创建用于处理不同的DomDataExchange,而ResultMap则是管理sql中的输出结果的。在执行insert操作的时候,只需要从HashMap中取到对应的BaseStatement,并校验外部传来的param,获取param里的值,并重新构造一个sql语句,然后创建PreparedStatement,往PreparedStatement中注入这些values,最后执行,整个过程就基本执行了。

 

 整段代码如下:

 

 

 public int executeUpdate(RequestScope request, Transaction trans, Object parameterObject)
      throws SQLException {
    ErrorContext errorContext = request.getErrorContext();
    errorContext.setActivity("preparing the mapped statement for execution");
    errorContext.setObjectId(this.getId());
    errorContext.setResource(this.getResource());

    request.getSession().setCommitRequired(true);

    try {
      parameterObject = validateParameter(parameterObject);

      Sql sql = getSql();

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

      int rows = 0;

      errorContext.setMoreInfo("Check the parameter map.");
      Object[] parameters = parameterMap.getParameterObjectValues(request, parameterObject);

      errorContext.setMoreInfo("Check the SQL statement.");
      String sqlString = sql.getSql(request, parameterObject);

      errorContext.setActivity("executing mapped statement");
      errorContext.setMoreInfo("Check the statement or the result map.");
      rows = sqlExecuteUpdate(request, trans.getConnection(), sqlString, parameters);

      errorContext.setMoreInfo("Check the output parameters.");
      if (parameterObject != null) {
        postProcessParameterObject(request, parameterObject, parameters);
      }

      errorContext.reset();
      sql.cleanup(request);
      notifyListeners();
      return rows;
    } catch (SQLException e) {
      errorContext.setCause(e);
      throw new NestedSQLException(errorContext.toString(), e.getSQLState(), e.getErrorCode(), e);
    } catch (Exception e) {
      errorContext.setCause(e);
      throw new NestedSQLException(errorContext.toString(), e);
    }
  }

 

 

 

Update

     update跟insert就都是从一个娘胎肚子里出来的,只是SqlMapExecutorDelegate的update方法里,没有executeSelectKey的操作。

 

Query

     query就复杂点,因为要处理skipResults,maxResults,以及结果集,所以多出了RowHandler。

分享到:
评论
5 楼 qliulog 2011-02-23  
finallygo 写道
如果使用select last_insert_id(); 来获取插入记录的id,在多线程的情况下会不会出现取到另一个记录的id呢?

select last_insert_id()是针对connection,只要多个线程使用用不同的connection就不会有问题
4 楼 finallygo 2011-01-18  
如果使用select last_insert_id(); 来获取插入记录的id,在多线程的情况下会不会出现取到另一个记录的id呢?
3 楼 C_J 2010-12-16  
GeneralStatement的validateParameter方法是用来比较SQL中用户定义的parameterClass中写的Class和实际用户输入param的Class,如果不吻合,则抛出异常。

用XXX.class.isAssignableFrom(parameterClass)这种方式+instanceof方式验证。

回顾:GeneralStatement在解析ibatis的xml文件的时候为每个sql语句都初始化了parameterClass中的Class和resultClass中的Class对象。


关于RequestScope,RequestScope被缓存在了一个Map里面,开始没看懂干嘛的,后来慢慢发现这个跟ibatis的cache有关。


2 楼 C_J 2010-12-16  
继续呗:
在insert操作的时候,SqlMapExecutorDelegate中的insert方法有一个executeSelectKey方法,在InsertStatement类中还包含一个SelectKeyStatement的statement,用以支持类似以下语句:

<insert id="insertBlob" parameterClass="bo.BlobTest">
<![CDATA[
insert into blob_test (myblob) values (#myblob#)
]]>
<selectKey resultClass="java.lang.Integer" keyProperty="id">
<![CDATA[
select last_insert_id();
]]>
</selectKey>
</insert>



所以,如果一个INSERT的SQL语句包含selectKey标签,就会先执行executeSelectKey的查询方法,这个SelectKeyStatement也是BaseStatement的子类。
1 楼 C_J 2010-12-05  
    基本写完了,还有一些小地方和不明朗的地方,比如validateParameter方法,ExecuteListener接口,稍后补充上吧。

    望大家来拍拍转吧
   

相关推荐

    iBATIS-DAO-2.3.4.726.rar_com.ibatis.dao_iBATIS dao 2_iBatis DAO_

    在这个2.3.4.726版本的源码中,我们可以深入理解iBATIS DAO的工作原理,并通过添加注释来帮助我们更好地掌握其实现细节。 首先,iBATIS DAO的核心概念是SQL Maps,它们定义了数据库操作的SQL语句,并将其映射到Java...

    ibatis-2.3.4.726.jar,ibatis-2.3.0.677.jar,ibatis-2.3.3.720.jar下载

    这里提到的是iBATIS的三个不同版本的jar包:ibatis-2.3.4.726.jar、ibatis-2.3.0.677.jar以及ibatis-2.3.3.720.jar。 首先,让我们深入了解iBATIS的核心概念和功能: 1. **SQL Map配置**:iBATIS的核心是SQL Map...

    struts2_spring2.5_ibatis2.3_mysql架构

    *架构struts2_spring2.5_ibatis2.3 *mysql5.0 *jdk 1.6 *带有所有jar包,可直接运行 本实例实现了用户登陆,用户信息CRUD相关操作。让你感受到了ibatis做o/r mapping的方便快捷。 下次集成dwr进来 create ...

    ibatis-2.3.3.720.jar

    ibatis-2.3.3.720.jar

    ibatis2.3.4.8.jar 和 ibatis-2.3.4.726.jar两个版本的下载

    在本主题中,我们将深入探讨Ibatis的两个特定版本:ibatis2.3.4.8.jar和ibatis-2.3.4.726.jar。 Ibatis的核心概念包括映射器(Mapper)、SQL映射文件和SqlSession。映射器是Ibatis的主要组件,它定义了数据库操作与...

    ibatis-2.3.3.7.jar

    ibatis-2.3.3.7.jar

    ibatis UPDATE 动态XML与insert

    标题 "ibatis UPDATE 动态XML与insert" 涉及到的是MyBatis框架中对数据库数据进行更新(UPDATE)和插入(INSERT)操作的动态XML配置方式。MyBatis是一个优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射,...

    ibatis2.3-src

    《深入解析iBatis 2.3源码》 iBatis,作为一款经典的Java持久层框架,以其轻量级、灵活的特性深受开发者喜爱。本文将深入剖析iBatis 2.3的源码,帮助读者理解其内部机制,提升开发与优化数据库访问的能力。 1. **...

    iBATIS_Spring_struts_demo.rar_DEMO_Struct spring ibatis_ibatis j

    标题中的“iBATIS_Spring_struts_demo.rar_DEMO_Struct spring ibatis_ibatis j”表明这是一个关于集成iBATIS、Spring和Struts的演示项目。这个DEMO旨在展示如何在Java应用中有效地整合这三个框架,从而实现数据持久...

    ibatis-2.3.4.732.jar

    ibatis-2.3.4.732.jar

    ibatis-2.3.0.677.jar

    标题 "ibatis-2.3.0.677.jar" 指向的是一个特定版本的 iBATIS 库,即版本号为 2.3.0.677 的 JAR 文件。iBATIS 是一个开源的 Java 框架,主要用于简化数据库与应用程序之间的交互。它在早期广泛应用于企业级应用开发...

    ibatis-2.3.2.715

    总之,iBatis 2.3.2.715的源代码不仅为我们提供了深入学习和理解iBatis工作原理的机会,还为开发者提供了丰富的示例和详尽的文档,有助于我们在实际项目中更高效、更灵活地使用iBatis。通过对这些源代码的探索和学习...

    10_ibatis教程_ibatis-2.3.3.720.zip

    在Java代码中,我们通过定义接口并使用`@Select`, `@Insert`, `@Update`, `@Delete`等注解,或者直接继承SqlMapClientTemplate,来实现对数据库的操作。例如: ```java public interface UserService { @Select...

    ibatis-2.3.4.726最新API chm格式

    ibatis-2.3.4.726最新API chm格式 非常好用

    iBATIS_02_addSequence_add_del_update_sel_sellike

    在 iBATIS 中,添加数据通常是通过调用 SQL Map 中的 `&lt;insert&gt;` 标签来完成的。开发者定义一个 SQL 语句,并将 Java 对象的属性映射到 SQL 语句的占位符上,iBATIS 自动处理参数绑定和事务提交。 2. **删除...

    iBATIS_DBL-2.2.0.638.zip_iBATIS_DBL_ibatis_ibatis 2_ibatis2 src_

    这个压缩包提供了对iBatis 2.x版本的深入理解,特别是对于那些希望查看或学习iBatis 2.2.0.638源码的开发者来说,极具价值。 首先,让我们深入了解iBatis的核心概念。iBatis的主要功能是作为一个SQL映射框架,它将...

    ibatis-2.3

    在2.3版本中,Ibatis提供了许多关键功能,帮助开发人员更高效地处理数据库操作。 Ibatis的核心概念是Mapper,它是一个XML配置文件或者注解,用来定义SQL语句、存储过程以及参数映射和结果映射。这种分离使得SQL逻辑...

    ibatis-2.3.4.726-src-源代码

    《深入解析iBatis 2.3.4.726源码》 iBatis,作为一款轻量级的Java持久层框架,以其灵活、高效的特点,在许多项目中得到了广泛应用。本文将针对iBatis 2.3.4.726版本的源代码进行详尽解读,帮助开发者深入了解其内部...

    ibatis2.3源码

    【标题】"ibatis2.3源码"指的是开源的SQL映射框架iBATIS的2.3版本的源代码。iBATIS是Java平台上的一种轻量级持久层框架,它将SQL语句与Java代码分离,使得开发者可以更加灵活地处理数据库操作。 【描述】中的"可以...

    ibatis-2.3.2.715.jar

    《深入解析ibatis-2.3.2.715.jar》 在Java开发领域,Ibatis作为一个轻量级的持久层框架,以其灵活、易用的特点深受开发者喜爱。本次我们将详细探讨`ibatis-2.3.2.715.jar`这个特定版本的Ibatis库,它为开发者提供了...

Global site tag (gtag.js) - Google Analytics