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