Ibatis源码分析
整体架构
UML
描述
SqlMapClientImpl接到请求后,创建SqlMapSessionImpl对象(ThreadLocal,保证线程安全);
SqlMapSessionImpl交由内部的代理类SqlMapExecutorDelegate执行;
代理类获取相应的MappedStatement,交由MappedStatement对象执行;
MappedStatement交由SqlExecutor执行;
最终使用JDBC方式执行sql。
时序图
初始化
UML
时序图
描述
ibatis初始化的核心目标是构建SqlMapClientImpl对象,主要思想如下:
1. 构建NodeletParser配置文件解析类,它维护了节点XPath和对应处理方式的映射关系,并提供了节点的通用处理方法。
2. SqlMapConfigParser和SqlMapParser在构造方法中向NodeletParser中添加节点XPath和对应处理方式的映射关系。
3. 配置文件解析采用递归方式进行,首先生成当前节点的XPath信息,再从NodeletParser获取对应的处理方式并执行。
4. 整个解析过程中每个节点生成的数据统一注入到XmlParserState,最终通过XmlParserStat获取SqlMapClientImpl对象并返回。
配置文件解析
类图
描述
创建SqlMapConfigParser时,创建vars 、SqlMapExecutorDelegate、SqlMapClientImpl,并注册各个节点对应的Nodelet定义。
public SqlMapConfigParser() {
this(null, null);
}
public SqlMapConfigParser(XmlConverter sqlMapConfigConv,XmlConverter sqlMapConv) {
super(new Variables());
parser.setValidation(true);
parser.setEntityResolver(new SqlMapClasspathEntityResolver());
vars.sqlMapConfigConv = sqlMapConfigConv;
vars.sqlMapConv = sqlMapConv;
vars.delegate = new SqlMapExecutorDelegate();
vars.typeHandlerFactory = vars.delegate.getTypeHandlerFactory();
vars.client = new SqlMapClientImpl(vars.delegate);
registerDefaultTypeAliases();
addSqlMapConfigNodelets();
addGlobalPropNodelets();
addSettingsNodelets();
addTypeAliasNodelets();
addTypeHandlerNodelets();
addTransactionManagerNodelets();
addSqlMapNodelets();
}
Vars作为一个上下文对象,存放整个解析过程中的变量。
开始调用parser.parse()解析
遇到"/sqlMapConfig/sqlMap"调用SqlMapParser解析,并将解析结果set到SqlMapExecutorDelegate对象中
从vars对象中返回client
是否必须将ParameterMap和 ResultMap的解析放到 sql的前面??、
参数映射
类图
描述
1. 从纵向上体现了Statement类的整体继承关系,MappedStatement接口提供了SQL执行上下文信息和执行操作,如ParameterMap、ResultMap、SQL、Timeout等上下文信息和executeQueryForList等操作信息;BaseStatement抽象类提供了MappedStatement的初步实现,它组合了MappedStatement需要的上下文信息;GeneralStatement实现类提供了MappedStatement的执行操作的基本实现;InsertStatement、SelectStatement等实现类提供了针对不同类型SQL操作的特定实现。
2. 从横向上体现了Statement类的初始化数据和请求处理数据的分离。 类图中的第一行对象(ParameterMap/ResultMap/SQL等)在初始化过程中会构造完毕,请求处理时直接获取即可; 类图中的第三行对象(RequestScope)在请求处理时才会创建,通过执行方法的参数传入,再进行后续处理。这是会对入参做出校验,并将参数事实射入sql中,拼接出最终可执行的sql
结果集映射
批量提交
Request / Session / transaction / connection
Session
1、session可以是代码请求的显式session,也可以是当线程使用SqlMapClient实例(即执行一条语句)自动获得的session。
2、一个session是附属于一个特定线程的,也就是说它是线程安全的,从下面创建session的过程中可以看出这点,当ThreadLoacle变量中存在session,则直接复用,当不存在时才新建:
private ThreadLocallocalSqlMapSession = new ThreadLocal();
……
private SqlMapSessionImpl getLocalSqlMapSession() {
SqlMapSessionImpl sqlMapSession =(SqlMapSessionImpl) localSqlMapSession.get();
if (sqlMapSession == null ||sqlMapSession.isClosed()) {
sqlMapSession = new SqlMapSessionImpl(this);
localSqlMapSession.set(sqlMapSession);
}
return sqlMapSession;
}
3、一个事务transaction会在执行中会独占一个session,运行时的session数必然要大于transaction数量。这也是Ibatis的参数:maxSessions必须大于maxTransactions的原因。代码参见TransactionManager.begin()方法:
public void begin(SessionScopesession, int transactionIsolation) throws SQLException, TransactionException {
Transactiontrans = session.getTransaction();
TransactionState state = session.getTransactionState();
if (state ==TransactionState.STATE_STARTED) {
throw newTransactionException("TransactionManager could not start a newtransaction. " +
"Atransaction is already started.");
} else if (state ==TransactionState.STATE_USER_PROVIDED) {
throw newTransactionException("TransactionManager could not start a newtransaction. " +
"A userprovided connection is currently being used by this session. " +
"The calling.setUserConnection (null) will clear the user provided transaction.");
}
txThrottle.increment();
try {
trans = transactionConfig.newTransaction(transactionIsolation);
session.setCommitRequired(false);
} catch (SQLException e) {
txThrottle.decrement();
throw e;
} catch(TransactionException e) {
txThrottle.decrement();
throw e;
}
session.setTransaction(trans);
session.setTransactionState(TransactionState.STATE_STARTED);
}
4、SqlMapSessionImpl类的作用是为SqlMapClientImpl加入会话控制
5、在构造SqlMapSessionImpl对象时,会创建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;
}
6、session的创建受到参数maxSessions的限制,如果当前活动session已经超过这个上限,则线程会一直等待别的线程释放session资源。、
protected SessionScope popSession() {
return (SessionScope)sessionPool.pop();
}
public Object pop() {
Object o = null;
while (o== null) {
try {
throttle.increment();
o = pool.remove(0);
} catch (Exception e) {
// threadcollision detected, retry
}
}
return o;
}
Request
1、调用一次SqlMapClient,会产生一个Request对象,它属于一个session(会话)中
public int update(SessionScope session, String id, Object param) throws SQLException {
………….
RequestScope request =popRequest(session, ms);
……..
}
protected RequestScope popRequest(SessionScopesession, MappedStatement mappedStatement) {
RequestScope request = (RequestScope) requestPool.pop();
session.incrementRequestStackDepth();
request.setSession(session);
mappedStatement.initRequest(request);
return request;
}
2、每个SqlMapExecutorDelegate实例存在一个requestPool,总共有maxRequest大小,如果请求到来时,requestPool已用尽,则需等待。
public Object pop() {
Object o = null;
while (o == null) {
try {
throttle.increment();
o = pool.remove(0);
} catch (Exception e) {
// threadcollision detected, retry
}
}
return o;
}
3、一个Request的到来,可能开启一个新的transaction,也可能沿用该session对应的transaction,取决于客户端是否显示调用了SqlMapClientImpl. startTransaction()。如果显示调用,则该Request会被放入已有事务中处理,如果没有,则会新建一个事务,并被Ibatis自动提交,:public ObjectqueryForObject(SessionScope session, String id, Object paramObject, ObjectresultObject) throws SQLException {
Object object = null;
MappedStatement ms =getMappedStatement(id);
Transaction trans =getTransaction(session);
boolean autoStart = trans == null;
try {
trans =autoStartTransaction(session, autoStart, trans);
RequestScope request =popRequest(session, ms);
try {
object =ms.executeQueryForObject(request, trans, paramObject, resultObject);
} finally {
pushRequest(request);
}
autoCommitTransaction(session, autoStart);
} finally {
autoEndTransaction(session, autoStart);
}
return object;
}
private Transaction autoStartTransaction(SessionScope session, boolean autoStart, Transactiontrans) throws SQLException {
Transaction transaction = trans;
if (autoStart) {
session.getSqlMapTxMgr().startTransaction();
transaction =getTransaction(session);
}
// System.out.println(transaction);
return transaction;
}
private voidautoCommitTransaction(SessionScope session, boolean autoStart) throws SQLException {
if (autoStart) {
session.getSqlMapTxMgr().commitTransaction();
}
}
private voidautoEndTransaction(SessionScope session, boolean autoStart) throws SQLException {
if (autoStart) {
session.getSqlMapTxMgr().endTransaction();
}
}
transaction
transaction上可以挂很多request,transaction开始时,将autoCommit=false,然后transaction上可以挂很多request,等到事务提交时,统一将request对应的PreparedStatement提交至数据库。
Connection
每个transaction都持有一个connection,该connection在transaction结束时,会被放回连接池中,或者直接被销毁。所以transaction的大小依赖于DBMS的限制。
Thread ThreadLocal object
综上所述:
每次调用SqlClientMap都会产生一个Request;
每个transaction均会独占一个session,并在事务结束时,将session归还sessionPool
如果没有显示开启一个事务,ibatis会为每个request自动开启一个transaction,如果是查询交易,则不会提交事务,如果是update,则会自动提交事务,最后会自动回收事务和session资源。这时一个request会占用一个transaction,一个session
如果显示开始一个transaction,则事务中的所有request会被统一提交,这时一个transaction对应一个session,多个request。
MaxRequest >MaxSession > MaxTransaction
为什么MaxSession > MaxTransaction??从以上分析,实际上他们是相等的。
SqlMapClientImpl / SqlMapSessionImpl/SqlMapExecutorDelegate
SqlMapClientImpl
在一个应用中(如Spring容器)只会创建一个实例,在加载ibatis配置文件时完成
SqlMapSessionImpl
会创建多个对象实例,原因还是session存储了线程对应的资源和状态,无法共享,所以会为每个线程创建一个SqlMapSessionImpl实例,并将其存储在ThreadLocal对象中,ThreadLocal对象保存了每个线程对应于该变量的副本。
*******************ThreadLocal *************************************
线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。
在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。
概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
ThreadLocal vs synchronized
ThreadLocal使用场合主要解决多线程中数据数据因并发产生不一致问题。ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,单大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。
ThreadLocal不能使用原子类型,只能使用Object类型。ThreadLocal的使用比synchronized要简单得多。
ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别。synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。
Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。
当然ThreadLocal并不能替代synchronized,它们处理不同的问题域。Synchronized用于实现同步机制,比ThreadLocal更加复杂。
SqlMapExecutorDelegate
只会创建一个对象实例,在加载ibatis配置文件时完成
一次带事务的更新
UML类图
描述
1、 startTransaction
SqlMapClientImpl创建一个新的SqlMapSessionImpl实例,持有它并将其放入ThreadLocal中,在创建SqlMapSessionImpl对象时,会创建一个SessionScope(简称session)对象,并持有它;
SqlMapExecutorDelegate调用TransactionManager的begin方法开始事务,这个过程中会创建一个JdbcTransaction对象,并将其配给session对象
返回。
2、 update
SqlMapClientImpl从ThreadLocal中获取SqlMapSessionImpl对象,由SqlMapSessionImpl对象调用SqlMapExecutorDelegate(简称delegate)的update方法,在这个过程中,delegate会获得预先缓存好的MappedStatement对象(简称statement),transaction对象,并从RequestPool中pop一个Reaquest对象,然后调用statement.excuteUpdate(request,transaction,params),此方法会调用transaction.getConnection()获得一个Connection对象,最后执行PreparedStatement。
3、 commitTransaction
调用connection.commit()提交事务
4、 endTransaction
如果第3步失败,则调用connection.rollback()回滚事务,并调用transaction.close()将transaction和connection关闭和放空;将sessiion对象push回sessionPool