论坛首页 Java企业应用论坛

读ibatis源码—为什么说SqlMapClient是线程安全的

浏览 6762 次
精华帖 (0) :: 良好帖 (3) :: 新手帖 (8) :: 隐藏帖 (0)
作者 正文
   发表时间:2009-12-31  
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,这里保存的是每个执行语句的状态。这里就不看了。

第一次分析代码,思路有点混乱哈,自己挺有收获的,给大家分享一下,欢迎拍砖哈。
   发表时间:2010-01-08  
分析的不错
0 请登录后投票
   发表时间:2010-01-08  

最后就剩下localSqlMapSession字段了,其实有经验的同学一眼就能看出来这点的,ThreadLocal就是为处理线程安全而来的,他的实质为每个线程保存一个副本。他的实现就是存在一个全局的Map存放localSqlMapSession,key是线程的id号value值是一个localSqlMapSession的副本。
SqlMapClientImpl里面的方法:
    
     这段话我的理解不同,ThreadLocal是一个变量,
Thread类中有一个ThreadLocalMap变量,开始是null
每个线程通过
它将ThreadLocal作为key值保存相应的value
 
      
0 请登录后投票
   发表时间:2010-01-11  
ysen 写道


     这段话我的理解不同,ThreadLocal是一个变量,
Thread类中有一个ThreadLocalMap变量,开始是null
每个线程通过
它将ThreadLocal作为key值保存相应的value
 
      

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            return (T)map.get(this);

        // Maps are constructed lazily.  if the map for this thread
        // doesn't exist, create it, with this ThreadLocal and its
        // initial value as its only entry.
        T value = initialValue();
        createMap(t, value);
        return value;
    }

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

以上是ThreadLocal的get,set方法的源码,从中我们可以看出,我们用的ThreadLocal实质是一个map,没一个ThreadLocal变量都是一个map,key为线程Id
0 请登录后投票
   发表时间:2010-01-18  
精神可嘉,这是线程安全的标准实现方式,但不是最高效的方式,其实iBATIS官方文档说得很清楚了。
0 请登录后投票
   发表时间:2010-03-28  
ThreadLocal的并不是起着实现线程安全的作用。使用了ThreadLocal还是会出现线程安全的问题
ThreadLocal是为了提供一个跨方法,跨类的变量副本保存。一个ThreadLocal保存一个
0 请登录后投票
论坛首页 Java企业应用版

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