题记:
废话不多,慢慢写吧
写的可能有点乱,但思路是按照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。
转载自:
http://www.iteye.com/topic/832157
Ibatis做为一个半自动化的Orm框架有他的缺点和优点。在这里我就不宽泛的说这些了。就说说为什么SqlMapClient是线程安全的,他是怎么实现的。
提出问题:
- private static SqlMapClient sqlMapper;
- /**
- * It's not a good idea to put code that can fail in a class initializer,
- * but for sake of argument, here's how you configure an SQL Map.
- */
- static {
- try {
- Reader reader = Resources.getResourceAsReader("com/mydomain/data/SqlMapConfig.xml");
- sqlMapper = SqlMapClientBuilder.buildSqlMapClient(reader);
- reader.close();
- } catch (IOException e) {
- // Fail fast.
- throw new RuntimeException("Something bad happened while building the SqlMapClient instance." + e, e);
- }
- }
这是一段ibatis simple工程的代码,大家都能看明白这是一个单例,只有一个SqlMapClient对象存在,在多线程的情况下,SqlMapClient是怎么解决事务隔离呢,怎么共享资源的呢?
一、 SqlMapClient是怎么被创建的
打开SqlMapClientBuilder发现buildSqlMapClien一句话
- public static SqlMapClient buildSqlMapClient(Reader reader) {
- // return new XmlSqlMapClientBuilder().buildSqlMap(reader);
- return new SqlMapConfigParser().parse(reader);
- }
我们顺着这条线一路看下去
SqlMapConfigParser类的做了两件事把reader交个一个NodeletParser去解析reader(也就是我们的配置文件),在一个就是XmlParserState的一个属性产生一个SqlMapClient对象
- public class SqlMapConfigParser {
- protected final NodeletParser parser = new NodeletParser();
- private XmlParserState state = new XmlParserState();
- public SqlMapClient parse(Reader reader) {
- try {
- usingStreams = false;
- parser.parse(reader);
- return state.getConfig().getClient();
- } catch (Exception e) {
- throw new RuntimeException("Error occurred. Cause: " + e, e);
- }
- }
打开NodeletParser的parse方法,我们发现他就是解析xml配置文件的
- public void parse(Reader reader) throws NodeletException {
- try {
- Document doc = createDocument(reader);
- parse(doc.getLastChild());
- } catch (Exception e) {
- throw new NodeletException("Error parsing XML. Cause: " + e, e);
- }
- }
最后这些文件被分门别类的放在了XmlParserState的这些属性里
- private SqlMapConfiguration config = new SqlMapConfiguration();
- private Properties globalProps = new Properties();
- private Properties txProps = new Properties();
- private Properties dsProps = new Properties();
- private Properties cacheProps = new Properties();
- private boolean useStatementNamespaces = false;
- private Map sqlIncludes = new HashMap();
- private ParameterMapConfig paramConfig;
- private ResultMapConfig resultConfig;
- private CacheModelConfig cacheConfig;
- private String namespace;
- private DataSource dataSource;
现在我们回过头看return state.getConfig().getClient();
是这句话获得了SqlMapClient对象,这个对象是怎么创建的呢,在SqlMapConfiguration的构造方法里面就已经创建好了。
- public SqlMapConfiguration() {
- errorContext = new ErrorContext();
- delegate = new SqlMapExecutorDelegate();
- typeHandlerFactory = delegate.getTypeHandlerFactory();
- client = new SqlMapClientImpl(delegate);
- registerDefaultTypeAliases();
- }
原来我们的到的并不是SqlMapClient(接口不能实现)对象,而是他的一个实现SqlMapClientImpl
二、 深入SqlMapClientImpl内部
SqlMapClientImpl类中只有三个字段
- private static final Log log = LogFactory.getLog(SqlMapClientImpl.class);
- public SqlMapExecutorDelegate delegate;
- protected ThreadLocal localSqlMapSession = new ThreadLocal();
log是一个日志记录的对象,与线程安全肯定是无关的
SqlMapExecutorDelegate这个类里面有什么东西呢
- private static final Probe PROBE = ProbeFactory.getProbe();
- private boolean lazyLoadingEnabled;
- private boolean cacheModelsEnabled;
- private boolean enhancementEnabled;
- private boolean useColumnLabel = true;
- private boolean forceMultipleResultSetSupport;
- private TransactionManager txManager;
- private HashMap mappedStatements;
- private HashMap cacheModels;
- private HashMap resultMaps;
- private HashMap parameterMaps;
- protected SqlExecutor sqlExecutor;
- private TypeHandlerFactory typeHandlerFactory;
- private DataExchangeFactory dataExchangeFactory;
- private ResultObjectFactory resultObjectFactory;
- private boolean statementCacheEnabled;
这些属性都是一些关于跟sqlMap配置的一些信息,这些信息和线程安全也没有很大的关系。
最后就剩下localSqlMapSession字段了,其实有经验的同学一眼就能看出来这点的,ThreadLocal就是为处理线程安全而来的,他的实质为每个线程保存一个副本。他的实现就是存在一个全局的Map存放localSqlMapSession,key是线程的id号value值是一个localSqlMapSession的副本。
SqlMapClientImpl里面的方法:
- public Object insert(String id, Object param) throws SQLException {
- return getLocalSqlMapSession().insert(id, param);
- }
- public Object insert(String id) throws SQLException {
- return getLocalSqlMapSession().insert(id);
- }
- public int update(String id, Object param) throws SQLException {
- return getLocalSqlMapSession().update(id, param);
- }
- public int update(String id) throws SQLException {
- return getLocalSqlMapSession().update(id);
- }
- public int delete(String id, Object param) throws SQLException {
- return getLocalSqlMapSession().delete(id, param);
- }
- public int delete(String id) throws SQLException {
- return getLocalSqlMapSession().delete(id);
- }
- public Object queryForObject(String id, Object paramObject) throws SQLException {
- return getLocalSqlMapSession().queryForObject(id, paramObject);
- }
多么熟悉的方法啊,这就是我们经常用的curd的方法。从代码上证明了我们的推测,线程安全就是和localSqlMapSession有关
虽然找到了相关的属性,但是他们是怎么实现的呢。
三、 线程安全的实现。
就dao部分的线程安全来说一个是主要是事务的完成性。如果事务能够保证完整性,那么就可以说是线程安全的。
localSqlMapSession存的是什么什么东西呢,我们打开代码看看。
- protected SqlMapSessionImpl getLocalSqlMapSession() {
- SqlMapSessionImpl sqlMapSession = (SqlMapSessionImpl) localSqlMapSession.get();
- if (sqlMapSession == null || sqlMapSession.isClosed()) {
- sqlMapSession = new SqlMapSessionImpl(this);
- localSqlMapSession.set(sqlMapSession);
- }
- return sqlMapSession;
- }
再研究一下SqlMapSessionImpl,这个类只有三个字段
protected SqlMapExecutorDelegate delegate;
protected SessionScope sessionScope;
protected boolean closed;
很明显SessionScope这是我们要找的东西
- private static long nextId;
- private long id;
- // Used by Any
- private SqlMapClient sqlMapClient;
- private SqlMapExecutor sqlMapExecutor;
- private SqlMapTransactionManager sqlMapTxMgr;
- private int requestStackDepth;
- // Used by TransactionManager
- private Transaction transaction;
- private TransactionState transactionState;
- // Used by SqlMapExecutorDelegate.setUserProvidedTransaction()
- private TransactionState savedTransactionState;
- // Used by StandardSqlMapClient and GeneralStatement
- private boolean inBatch;
- // Used by SqlExecutor
- private Object batch;
- private boolean commitRequired;
- private Map preparedStatements;
根据我们的分析事务的完整性足以保证dao层的线程安全。Transaction保存在ThreadLocal里面证明了SqlMapClient是线程安全的,我们在整个工程中只要一个SqlMapClient对象就够了。
再来看下SessionScope这个类的字段
private SqlMapClient sqlMapClient;保存的是一个SqlMapClient
private SqlMapExecutor sqlMapExecutor; 执行sql用的
private SqlMapTransactionManager sqlMapTxMgr; 管理事务的
private int requestStackDepth;
// Used by TransactionManager
private Transaction transaction; 事务
private TransactionState transactionState; 事务的状态
// Used by SqlMapExecutorDelegate.setUserProvidedTransaction()
private TransactionState savedTransactionState; 事务的保存状态
// Used by StandardSqlMapClient and GeneralStatement
private boolean inBatch;是否批处理
// Used by SqlExecutor
private Object batch;
private boolean commitRequired;是否用提交
private Map preparedStatements;这个应该是保存批处理的PreparedStatement
我们突然发现没有连接类Connection,如果用jdbc的话Connection是多么重要的一个对象啊,在这里没有保存Connection呢。打开JdbcTransaction(一个Transaction的实现)
- private static final Log connectionLog = LogFactory.getLog(Connection.class);
- private DataSource dataSource;
- private Connection connection;
- private IsolationLevel isolationLevel = new IsolationLevel();
- public JdbcTransaction(DataSource ds, int isolationLevel) throws TransactionException {
- // Check Parameters
- dataSource = ds;
- if (dataSource == null) {
- throw new TransactionException("JdbcTransaction initialization failed. DataSource was null.");
- }
- this.isolationLevel.setIsolationLevel(isolationLevel);
- }
- private void init() throws SQLException, TransactionException {
- // Open JDBC Transaction
- connection = dataSource.getConnection();
- if (connection == null) {
- throw new TransactionException("JdbcTransaction could not start transaction. Cause: The DataSource returned a null connection.");
- }
- // Isolation Level
- isolationLevel.applyIsolationLevel(connection);
- // AutoCommit
- if (connection.getAutoCommit()) {
- connection.setAutoCommit(false);
- }
- // Debug
- if (connectionLog.isDebugEnabled()) {
- connection = ConnectionLogProxy.newInstance(connection);
- }
- }
- public void commit() throws SQLException, TransactionException {
- if (connection != null) {
- connection.commit();
- }
- }
- public void rollback() throws SQLException, TransactionException {
- if (connection != null) {
- connection.rollback();
- }
- }
- public void close() throws SQLException, TransactionException {
- if (connection != null) {
- try {
- isolationLevel.restoreIsolationLevel(connection);
- } finally {
- connection.close();
- connection = null;
- }
- }
- }
- public Connection getConnection() throws SQLException, TransactionException {
- if (connection == null) {
- init();
- }
- return connection;
- }
原来Connection在这里保存着呢,事务的提交,回滚也是在这里实现的。
到这里大致明白了,ibatis为每一个操作SqlMapClient的线程建立一个SessionScope对象,这里面保存了Transaction,Connection,要执行的PreparedStatement。
SqlMapClient对象里面保存的是全局有关的缓存策略,ParameterMap,ResultMap,jdbc到Java对象的类型转换,别名等信息。
在每个执行的Statement中还有一个StatementScope,这里保存的是每个执行语句的状态。这里就不看了。
第一次分析代码,思路有点混乱哈,自己挺有收获的,给大家分享一下,欢迎拍砖哈。