(三)MyBatisSqlSession创建及简要解析----MyBatis源码解析

       上一篇我们介绍了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定义的用户层接口,使用该接口基本能满足用户(调用客户端)访问数据库的基本要求。由于接口定义的代码和注释比较多,这里就不贴了。其主要的方法如下:

  1. select类方法
  2. update/insert/delete方法
  3. commit()
  4. rollback()
  5. 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的五个方法,可以整理出如下关系。

  1. DefaultSqlSession持有一个Executor对象,默认为SimpleExecutor,如果没有设置缓存的话。
  2. Executor持有一个Transaction对象
  3. DefaultSqlSession将select/update/insert/delete/commit/rollback/close交由Executor处理
  4. Executor又将commit/rollback/close方法交由Transaction处理
  5. DefaultSqlSession/Executor/Transaction对象都在DefaultSqlSessionFactory.openSessionFromDataSource方法中创建

接下来咱们SqlSessionFactory和SqlSession对象的范围和线程安全

可以看到到官方的线程安全与否的观点

  1. SqlSessionFactory可以在整个应用程序中保持一个单例,也就是它是线程安全的
  2. 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--;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值