上一篇我们介绍了mybatis动态代理,文章末尾也写了要继续分析excutor,但是第三讲再讲解excutor执行分析之前,我必须要带领大家回顾下SqlSession的创建过程,这样有助于接下来excutor的理解。
-
SqlSession的使用简单
当我们得到sqlsessionFactory后便可以获取sqlsession对象了,sqlsession对象的生命周期都有以下过程,如下方法所示:
public static void useSqlSession(SqlSessionFactory sqlSessionFactory){
//在通过SqlSessionFactory获取一个SqlSession
SqlSession sqlSession=sqlSessionFactory.openSession();
//用标准的try/catch/finally写法操作数据库
try{
//select
//update等待操作
//提交事务
sqlSession.commit();
}catch(Exception e){
//出错,回滚事务
sqlSession.rollback();
}finally{
//关闭
sqlSession.close();
}
}
-
SqlSession接口定义
SqlSession定义了操作数据库的基本,这个Mybatis定义的用户层接口,使用该接口基本能满足用户(调用客户端)访问数据库的基本要求。由于接口定义的代码和注释比较多,这里就不贴了。其主要的方法如下:
- select类方法
- update/insert/delete方法
- commit()
- rollback()
- close()
如果了解过jdbc,肯定知道这些方法的用途!
-
SqlSession的创建过程
SqlSessionFactoryBuilder.build()方法会创建一个DefaultSqlSessionFactory对象,SqlSessionFactoryBuilder中最后一个方法,代码如下:
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
接下来咱们再看下如何打开session,DefaultSqlSessionFactory.openSession()方法 主要看DefaultSqlSessionFactory
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
//这个是最终创建SqlSession对象的方法,需要三个参数
//execType,这个示例使用的是configuration.getDefaultExecutorType(),即在Configuration默认配置的
//事务隔离等级,我们对数据库操作里一般都不会带这个属性,这个属性由数据库分配即可
//autoCommit:这个一般都是false,不然事务将没有意义
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, Boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
//获取一个事务工厂,这个也是在配置文件中配置的
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
//通过事务工厂获取一个事务
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//根据execType获取一个Executor,这个稍后再详细讨论
final Executor executor = configuration.newExecutor(tx, execType);
//创建SqlSession对象,这里创建的DefaultSqlSession
return new DefaultSqlSession(configuration, executor, autoCommit);
}catch (Exception e) {
closeTransaction(tx);
// may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
}finally {
ErrorContext.instance().reset();
}
}
当我们查看XMLConfigBuilder,可以看到默认的execType为SIMPLE-----即为SimpleExecutor
configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
//默认
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
事务管理则是通过mybatis-config中的配置获取事务节点大多数采用数据库事务,也就是JdbcTransactionFactory。
<environment id="development"><!-- 开发环境 -->
//事务工厂
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
看完上述的session创建过程,小伙伴们是不是已经对这个过程比较清晰了那?那咱们接下来就要具体看下DefaultSqlSession的增、删、改、查等的具体方法的实现代码。
首先我们看下select方法:
//执行查询语句
public <e> List<e> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
//交由executor处理
List<e> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
return result;
}catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
}finally {
ErrorContext.instance().reset();
}
}
接下来我们看update,由于insert\delete方法类似所以就不贴代码了,有兴趣的可以下载源码看下DefaultSqlSession类。
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
//交由executor处理
return executor.update(ms, wrapCollection(parameter));
}catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
}finally {
ErrorContext.instance().reset();
}
}
看完了这些基础操作,事务提交以及回滚自然不能缺少。那我们再来看下事务的提交和回滚方法。
//事务提交
public void commit(boolean force) {
try {
//交由executor处理
executor.commit(isCommitOrRollbackRequired(force));
dirty = false;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
//事务回滚
public void rollback(boolean force) {
try {
//交由executor处理
executor.rollback(isCommitOrRollbackRequired(force));
dirty = false;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error rolling back transaction. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
//关闭
public void close() {
try {
//交由executor处理
executor.close(isCommitOrRollbackRequired(false));
dirty = false;
} finally {
ErrorContext.instance().reset();
}
}
上面方法可以看出,DefaultSqlSession主要的操作都是交由Executor处理,这应该是设计模式中的适配器模式!
在看executor的五个方法,可以整理出如下关系。
- DefaultSqlSession持有一个Executor对象,默认为SimpleExecutor,如果没有设置缓存的话。
- Executor持有一个Transaction对象
- DefaultSqlSession将select/update/insert/delete/commit/rollback/close交由Executor处理
- Executor又将commit/rollback/close方法交由Transaction处理
- DefaultSqlSession/Executor/Transaction对象都在DefaultSqlSessionFactory.openSessionFromDataSource方法中创建
接下来咱们SqlSessionFactory和SqlSession对象的范围和线程安全
可以看到到官方的线程安全与否的观点
- SqlSessionFactory可以在整个应用程序中保持一个单例,也就是它是线程安全的
- SqlSession则需要每个线程持有不同的对象,也就是说它不是线程安全的。
如下是官方的说明
- DefaultSqlSessionFactory生成SqlSession的方法
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
上面代码采用了线程封闭的技术,也就是说将对象封闭在当前线程范围内,保证这些对象其他线程不可见,这样就保证了自已的线程安全性。但有一个例外:就是用到了Congifuration,它是对其他线程可见的,但这个Configuration对象是实际不可变的,所以DefaultSqlSessionFactory是线程安全的。线程封闭和实际不可变对象,这两个概念在<<java并发编程实践>>一书有详细的说明。
- DefaultSqlSession线程安全性分析 ,通过以下三处代码查看你可以得到结论 Executor不是一线程安全的,SqlSession对他的访问没有使用同步机制,所以SqlSession并不是线程安全的
DefaultSqlSession有一个Executor对象的引用,对其的访问也没有使用线程安全的机制:
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
//这里没有用synchronized或lock
List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
return result;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
而再看BaseExecutor,这个并不是线程安全的
protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.transaction = transaction;
this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();
//前面对PerpetualCache已经分析过,PerpetualCache是用HashMap实现的,并不是线程安全的
this.localCache = new PerpetualCache("LocalCache");
this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
this.closed = false;
this.configuration = configuration;
this.wrapper = this;
}
再看他对cache的访问,也没用使用同步
try {
queryStack++;
//这里访问localCache.getObject()并没有使用同步
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}