当前位置: 代码迷 >> SQL >> SqlMapClient是线程保险的
  详细解决方案

SqlMapClient是线程保险的

热度:76   发布时间:2016-05-05 13:21:03.0
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,这里保存的是每个执行语句的状态。这里就不看了。