精华帖 (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。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2010-12-05
基本写完了,还有一些小地方和不明朗的地方,比如validateParameter方法,ExecuteListener接口,稍后补充上吧。
望大家来拍拍转吧 |
|
返回顶楼 | |
发表时间: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的子类。 |
|
返回顶楼 | |
发表时间: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有关。 |
|
返回顶楼 | |
发表时间:2011-01-18
如果使用select last_insert_id(); 来获取插入记录的id,在多线程的情况下会不会出现取到另一个记录的id呢?
|
|
返回顶楼 | |
发表时间:2011-02-23
finallygo 写道 如果使用select last_insert_id(); 来获取插入记录的id,在多线程的情况下会不会出现取到另一个记录的id呢?
select last_insert_id()是针对connection,只要多个线程使用用不同的connection就不会有问题 |
|
返回顶楼 | |
发表时间:2011-07-27
多谢楼主分享,偶像啊!
|
|
返回顶楼 | |
发表时间:2011-07-29
正如楼主所说: 在整个app多线程中,可以共用一个SqlMapClient来执行操作,,,我的问题是,,,既然有了多线程,,是不是就该考虑线程同步问题,,,请教楼主,,,,ibatis中控制线程同步该做 如何解释?
|
|
返回顶楼 | |
发表时间:2011-07-29
最后修改:2011-07-29
我想问题1回答了你的问题,ThreadLocal,简单的说:就是一个对象做N份拷贝到每个Thread的ThreadLocalMap中,这样就线程同步了
|
|
返回顶楼 | |
浏览 5221 次