论坛首页 Java企业应用论坛

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

浏览 5223 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2010-12-03   最后修改:2010-12-22

题记:

    废话不多,慢慢写吧

 

写的可能有点乱,但思路是按照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。

   发表时间:2010-12-05  
    基本写完了,还有一些小地方和不明朗的地方,比如validateParameter方法,ExecuteListener接口,稍后补充上吧。

    望大家来拍拍转吧
   
0 请登录后投票
   发表时间:2010-12-16   最后修改: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的子类。
0 请登录后投票
   发表时间: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有关。


0 请登录后投票
   发表时间:2011-01-18  
如果使用select last_insert_id(); 来获取插入记录的id,在多线程的情况下会不会出现取到另一个记录的id呢?
0 请登录后投票
   发表时间:2011-02-23  
finallygo 写道
如果使用select last_insert_id(); 来获取插入记录的id,在多线程的情况下会不会出现取到另一个记录的id呢?

select last_insert_id()是针对connection,只要多个线程使用用不同的connection就不会有问题
0 请登录后投票
   发表时间:2011-07-27  
多谢楼主分享,偶像啊!
0 请登录后投票
   发表时间:2011-07-29  
正如楼主所说: 在整个app多线程中,可以共用一个SqlMapClient来执行操作,,,我的问题是,,,既然有了多线程,,是不是就该考虑线程同步问题,,,请教楼主,,,,ibatis中控制线程同步该做 如何解释?
0 请登录后投票
   发表时间:2011-07-29   最后修改:2011-07-29
我想问题1回答了你的问题,ThreadLocal,简单的说:就是一个对象做N份拷贝到每个Thread的ThreadLocalMap中,这样就线程同步了
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics