浏览 6762 次
精华帖 (0) :: 良好帖 (3) :: 新手帖 (8) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2009-12-31
提出问题: 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,这里保存的是每个执行语句的状态。这里就不看了。 第一次分析代码,思路有点混乱哈,自己挺有收获的,给大家分享一下,欢迎拍砖哈。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2010-01-08
分析的不错
|
|
返回顶楼 | |
发表时间:2010-01-08
最后就剩下localSqlMapSession字段了,其实有经验的同学一眼就能看出来这点的,ThreadLocal就是为处理线程安全而来的,他的实质为每个线程保存一个副本。他的实现就是存在一个全局的Map存放localSqlMapSession,key是线程的id号value值是一个localSqlMapSession的副本。 SqlMapClientImpl里面的方法: 这段话我的理解不同,ThreadLocal是一个变量, Thread类中有一个ThreadLocalMap变量,开始是null 每个线程通过 它将ThreadLocal作为key值保存相应的value |
|
返回顶楼 | |
发表时间: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 |
|
返回顶楼 | |
发表时间:2010-01-18
精神可嘉,这是线程安全的标准实现方式,但不是最高效的方式,其实iBATIS官方文档说得很清楚了。
|
|
返回顶楼 | |
发表时间:2010-03-28
ThreadLocal的并不是起着实现线程安全的作用。使用了ThreadLocal还是会出现线程安全的问题
ThreadLocal是为了提供一个跨方法,跨类的变量副本保存。一个ThreadLocal保存一个 |
|
返回顶楼 | |