`

读ibatis源码—为什么说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是怎么解决事务隔离呢,怎么共享资源的呢?

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

第一次分析代码,思路有点混乱哈,自己挺有收获的,给大家分享一下,欢迎拍砖哈。
分享到:
评论
6 楼 J-catTeam 2010-03-28  
ThreadLocal的并不是起着实现线程安全的作用。使用了ThreadLocal还是会出现线程安全的问题
ThreadLocal是为了提供一个跨方法,跨类的变量副本保存。一个ThreadLocal保存一个
5 楼 fight_bird 2010-01-18  
精神可嘉,这是线程安全的标准实现方式,但不是最高效的方式,其实iBATIS官方文档说得很清楚了。
4 楼 tou3921 2010-01-14  
很好//////
3 楼 sunhj000java 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
2 楼 ysen 2010-01-08  

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

相关推荐

    ibatis源码,ibatis源码 ibatis源码 ibatis源码

    ibatis的原码 ibatis源码 ibatis源码 ibatis源码

    ibatis源码

    ibatis框架源码剖析书中附带的光盘,ibatis源码分析

    iBATIS框架源码剖析源码

    iBATIS框架源码剖析源码 iBATIS框架源码剖析源码 iBATIS框架源码剖析源码

    iBATIS框架源码剖析

    iBATIS框架源码剖析

    最新ibatis 源码

    ibatis源码 学习参考 对于学习ibatis很有帮助

    ibatis源码 例子

    ibatis 源码 例子 包含 源码,jar都有 部分代码 package com.icss.dao; import java.io.IOException; import java.io.Reader; import java.sql.SQLException; import java.util.List; import ...

    iBatis框架源码剖析

    iBATIS一词来源于“internet”和“abatis”的组合,是一个由Clinton Begin在2001年发起的开放源代码项目。于2010年6月16号被谷歌托管,改名为MyBatis。是一个基于SQL映射支持Java和·NET的持久层框架。

    ibatis框架源码剖析光盘资料

    mybatis的前身ibatis源码剖析

    springMVC+ibatis的源码

    springmvc+ibatis写的开发源码,对学习springmvc+ibatis的框架搭建和配置有帮助,下载后能够在eclipse中运行。

    ibatis源码及实例

    iBATIS一词来源于“internet”和“abatis”的组合,是一个由Clinton Begin在2001年发起的开放源代码项目。最初侧重于密码软件的开发,现在是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data ...

    ibatis源码+api文档+jar包

    在csdn找它一上午花了31分,全都不行,还是从其他网上找到的,贡献出来, 各位注意如果你要看源码,最好也是用本zip包中的jar,不然debug看源码时,会出莫名其妙的错误

    ibatis 2.3.4 源码

    ibatis 2.3.4 的源码 public abstract Object insert(String paramString, Object paramObject) throws SQLException; public abstract Object insert(String paramString) throws SQLException; public ...

    ibatisDemo 入门源码

    ibatisDemo 入门源码

    ibatis 学习源码

    PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN" "http://www.ibatis.com/dtd/sql-map-config-2.dtd"> cacheModelsEnabled="true" enhancementEnabled="true" lazyLoadingEnabled="true" ...

    iBatis源码jar包以后上传

    后继还有官方例子iBatis源码jar包以后上传

    ibatis2.3源码

    ibatis2.3源码.可以直接与myeclipse进行关联的那种,有需要的朋友可以看看。如果先进行评论再下载的话,就相当于是免费的。我会陆续上传其它源码的。

Global site tag (gtag.js) - Google Analytics