`

读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,一个优秀的Java持久层框架,以其轻量级、灵活的特性在众多ORM(Object-Relational Mapping)框架中独树一帜。iBatis的核心在于它的SQL映射机制,它将数据库操作与业务逻辑解耦,...

    ibatis源码

    描述中的"ibatis框架源码剖析书中附带的光盘,ibatis源码分析"暗示这可能是一个学习资源,用于深入理解iBATIS的工作原理,可能包括了对源码的详细解读和分析。 **iBATIS核心知识点** 1. **SQL映射**:iBATIS的核心...

    iBATIS框架源码剖析源码

    iBATIS是一个开源的Java框架,它为数据库访问提供了一个简单而强大的解决方案。这个框架的主要目的是将SQL映射到Java代码中,使得开发者能够避免直接处理JDBC的繁琐过程,从而更加专注于业务逻辑的实现。iBATIS的...

    iBATIS框架源码剖析

    iBATIS框架源码剖析

    最新ibatis 源码

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

    ibatis源码+api文档+jar包

    本资源包含了Ibatis的源码、API文档以及jar包,对于深入理解和使用Ibatis非常有帮助。 首先,让我们详细了解Ibatis的核心概念和功能: 1. SQL Map配置文件:Ibatis的核心是SQL Map配置文件,其中包含了SQL语句和...

    ibatis框架源码剖析光盘资料

    总的来说,ibatis框架源码的学习不仅可以帮助我们理解其工作原理,提升开发效率,还能为我们提供一种思考问题的角度,理解数据访问层的设计模式。通过对源码的深入剖析,我们可以更好地解决实际项目中的问题,进行...

    ibatis2.3源码

    【标题】"ibatis2.3源码"指的是开源的SQL映射框架iBATIS的2.3版本的源代码。iBATIS是Java平台上的一种轻量级持久层框架,它将SQL语句与Java代码分离,使得开发者可以更加灵活地处理数据库操作。 【描述】中的"可以...

    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的持久层框架。

    iBATIS2.3.4 jar包及源码

    iBATIS 是一款著名的开源Java持久层框架,它在2005年由Clinton Begin创建,最初命名为Apache MyBatis,后来发展为独立的项目。在本主题中,我们关注的是iBATIS 2.3.4版本的jar包及其源码。 首先,`ibatis-2.3.4.jar...

    基于Spring+Ibatis的安全线程实现

    在多线程环境中,线程安全是个关键问题。当多个线程并发访问共享资源时,如果没有正确的同步机制,可能会导致数据不一致、死锁等问题。在Spring框架中,我们可以通过以下几种方式实现线程安全: 1. **ThreadLocal**...

    springMVC+ibatis的源码

    通过学习和分析这个源码,开发者不仅可以深入了解SpringMVC和iBatis的协同工作原理,还可以掌握如何在Eclipse这样的IDE中配置和运行这样的项目。这有助于提升对MVC模式的理解,提高数据库操作的能力,以及熟练运用...

    ibatis源码及实例

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

    iBatis源码jar包以后上传

    这个“iBatis源码jar包以后上传”可能指的是将要分享或者提供iBatis的源码jar包,以便于开发者深入学习和理解其内部工作原理。 首先,让我们来了解一下iBatis的基本概念和工作流程。iBatis的核心是SQL Map配置文件...

Global site tag (gtag.js) - Google Analytics