SqlMapClient是线程安全的

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是怎么解决事务隔离呢,怎么共享资源的呢?
[newpage]
一、 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里面的方法: 

Java代码
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,这里保存的是每个执行语句的状态。这里就不看了。

你可能感兴趣的:(DAO,多线程,ibatis,jdbc,配置管理)