1. SqlSession 是如何被创建的? 每次的数据库操作都会创建一个新的SqlSession么?(也许有很多同学会说SqlSession是通过 SqlSessionFactory.openSession() 创建,但这个答案按照10分制顶多给5分)
  2. SqlSession与事务(Transaction)之间的关系? 在同一个方法中,Mybatis多次请求数据库,是否要创建多个SqlSession?
  3. SqlSession是如何实现数据库操作的?

    SqlSession

    SqlSession是⼀个接⼝,它有两个实现类:DefaultSqlSession (默认)和 SqlSessionManager(弃⽤,不做介绍)
    SqlSession是MyBatis中⽤于和数据库交互的顶层类,通常将它与ThreadLocal绑定,⼀个会话使⽤⼀ 个SqlSession,并且在使⽤完毕后需要close。
    SqlSession中的两个最重要的参数,configuration与Executor执⾏器

    创建SqlSession

    ```java

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { final Environment environment = configuration.getEnvironment(); // 创建了一个 TransactionFactory 事务工厂( 如果有仔细看过 SqlSessionFactoryBean.buildSqlSessionFactory() 过程的同学,应该能够看到是 SpringManagedTransactionFactory ) final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); // 通过 TransactionFactory 获取了一个 事务 Transaction tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); // 根据 execType(默认是 SIMPLE ) 获取了一个 Executor (真正执行数据库操作的对象) final Executor executor = configuration.newExecutor(tx, execType); // 返回了一个 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(); } }

  1. 整个SqlSession 的创建分 3个步骤:
  2. - 1 获取到 TransactionFactory 事务工厂对象 ( 如果有仔细看过 SqlSessionFactoryBean.buildSqlSessionFactory() 过程的同学,应该能够看到是 SpringManagedTransactionFactory )
  3. - 2 通过 TransactionFactory 获取了一个 事务 Transaction
  4. - 3 根据 execType(默认是 SIMPLE 获取了一个**Executor (真正执行数据库操作的对象)**
  5. - 4 创建并返回 DefaultSqlSession 对象
  6. 通过源码我们知道每次 SqlSession(准确地说是 DefaultSqlSession )的创建都会 有一个 Transaction(在Mybatis-Spring SpringManagedTransaction ) 事务对象 的生成。也就是说:
  7. - 1 **一个事务 Transaction 对象与一个 SqlSession 对象 是一一对应的关系。**
  8. - 2 **同一个SqlSession 不管执行多少次数据库操作。只要没有执行close,那么整个操作都是在同一个 Transaction 中执行的。**
  9. 看过之前的文章的同学应该有疑问,之前不管是创建MapperProxy SqlSession 还是 MapperMethod中调用的SqlSession其实都是 **SqlSessionTemplate** ,与这里的 **DefaultSqlSession** 不是同一个SqlSession对象。那么我们就来简单分析下这2者之间的区别与职责吧!
  10. <a name="ospQ9"></a>
  11. #### SqlSessionTemplate 与 DefaultSqlSession
  12. 在之前的文章中,我们讲到过 每创建一个 MapperFactoryBean 就会创建一个 SqlSessionTemplate 对象,而 MapperFactoryBean 在获取 MapperProxy 时会将 SqlSessionTemplate 传递到 MapperProxy中。 也就是说 SqlSessionTemplate 的生命周期是与 MapperProxy 的生命周期是一致的。( 注意: MapperProxy 是被注入到Spring容器中的,所以结果不难想象)<br />在之前的文章中,我们简单的描述过 SqlSessionTemplate 内部维护了一个 sqlSessionProxy ,而 sqlSessionProxy 是通过动态代理创建的一个 SqlSession 对象, SqlSessionTemplate 数据库操作方法 insert/update 等等都是委托 sqlSessionProxy 来执行的。那么我们想看下这个 sqlSessionProxy 的创建:
  13. ```java
  14. public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
  15. PersistenceExceptionTranslator exceptionTranslator) {
  16. notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
  17. notNull(executorType, "Property 'executorType' is required");
  18. this.sqlSessionFactory = sqlSessionFactory;
  19. this.executorType = executorType;
  20. this.exceptionTranslator = exceptionTranslator;
  21. // 通过 Proxy.newProxyInstance() 动态代理创建的
  22. this.sqlSessionProxy = (SqlSession) newProxyInstance(
  23. SqlSessionFactory.class.getClassLoader(),
  24. new Class[] { SqlSession.class },
  25. new SqlSessionInterceptor());
  26. }

在创建 sqlSessionProxy 的代码中,我们可以发现其指定的代理对象是 SqlSessionInterceptor(SqlSession拦截器?),那么关键代码肯定在这个 SqlSessionInterceptor 中,查看 SqlSessionInterceptor发现其是一个内部类,其源码如下:

  1. private class SqlSessionInterceptor implements InvocationHandler {
  2. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  3. // 根据条件获取一个SqlSession(注意此时的SqlSession 是 DefaultSqlSession ),此时的SqlSession 可能是新创建的,也可能是上一次的请求的SqlSession。
  4. SqlSession sqlSession = getSqlSession(
  5. SqlSessionTemplate.this.sqlSessionFactory,
  6. SqlSessionTemplate.this.executorType,
  7. SqlSessionTemplate.this.exceptionTranslator);
  8. try {
  9. // 反射执行 SqlSession 方法
  10. Object result = method.invoke(sqlSession, args);
  11. // 判断当前的 SqlSession 是有事务,如果有则不commit
  12. if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
  13. // force commit even on non-dirty sessions because some databases require
  14. // a commit/rollback before calling close()
  15. sqlSession.commit(true);
  16. }
  17. return result;
  18. } catch (Throwable t) {
  19. Throwable unwrapped = unwrapThrowable(t);
  20. // 判断如果是PersistenceExceptionTranslator且不为空,那么就关闭当前会话,并且将sqlSession置为空防止finally重复关闭
  21. if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
  22. // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
  23. closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
  24. sqlSession = null;
  25. Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
  26. if (translated != null) {
  27. unwrapped = translated;
  28. }
  29. }
  30. throw unwrapped;
  31. } finally {
  32. //只要当前会话不为空, 那么就会关闭当前会话操作,关闭当前会话操作又会根据当前会话是否有事务来决定会话是释放还是直接关闭。
  33. if (sqlSession != null) {
  34. closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
  35. }
  36. }
  37. }
  38. }

整个 invoke 分5个步骤:

  • 1、 根据条件获取一个SqlSession(注意此时的SqlSession 是 DefaultSqlSession ),此时的SqlSession 可能是新创建的,也可能是上一次的请求的SqlSession。
  • 2、 反射执行 SqlSession 方法
  • 3、 判断当前的 SqlSession 是否由事务所管控,如果是则不commit
  • 4、 判断如果是PersistenceExceptionTranslator且不为空,那么就关闭当前会话,并且将sqlSession置为空防止finally重复关闭
  • 5、 只要当前会话不为空, 那么就会关闭当前会话操作,关闭当前会话操作又会根据当前会话是否有事务来决定会话是释放还是直接关闭。 ```java

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {

  1. notNull(sessionFactory, "No SqlSessionFactory specified");
  2. notNull(executorType, "No ExecutorType specified");
  3. // 通过 TransactionSynchronizationManager (Spring 的一个事务同步管理器) 获取到一个 SqlSessionHolder (从字面意思就应该明白其内部维护有 SqlSession)
  4. SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
  5. // 判断SqlSessionHolder是否为空、是否与事务同步
  6. if (holder != null && holder.isSynchronizedWithTransaction()) {
  7. if (holder.getExecutorType() != executorType) {
  8. throw new TransientDataAccessResourceException("Cannot change the ExecutorType when there is an existing transaction");
  9. }
  10. // 将引用计数增加1
  11. holder.requested();
  12. // 返回持有的 SqLSession
  13. return holder.getSqlSession();
  14. }
  15. // 如果从 事务同步管理器 没能获取到 一个 SqlSessionHolder 则 调用 sessionFactory.openSession() 新建一个SqlSession
  16. SqlSession session = sessionFactory.openSession(executorType);
  17. // 判断当前是否有事务,有则 根据 SqlSession 创建一个 SqlSessionHolder 并将其注册进入到 TransactionSynchronizationManager 中,以供当前事务中的下次使用
  18. if (TransactionSynchronizationManager.isSynchronizationActive()) {
  19. Environment environment = sessionFactory.getConfiguration().getEnvironment();
  20. if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
  21. holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
  22. TransactionSynchronizationManager.bindResource(sessionFactory, holder);
  23. TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
  24. holder.setSynchronizedWithTransaction(true);
  25. holder.requested();
  26. } else {
  27. if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
  28. if (logger.isDebugEnabled()) {
  29. logger.debug("SqlSession [" + session + "] was not registered for synchronization because DataSource is not transactional");
  30. }
  31. } else {
  32. throw new TransientDataAccessResourceException(
  33. "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
  34. }
  35. }
  36. } else {
  37. if (logger.isDebugEnabled()) {
  38. logger.debug("SqlSession [" + session + "] was not registered for synchronization because synchronization is not active");
  39. }
  40. }
  41. return session;

}

  1. 整个流程步骤分一下几步:
  2. - 1 通过 TransactionSynchronizationManager Spring 的一个事务同步管理器) 获取到一个 SqlSessionHolder (从字面意思就应该明白其内部维护有 SqlSession
  3. - 2 判断SqlSessionHolder是否为空、是否与事务同步,是则返回持有的 SqLSession
  4. - 3 如果从 事务同步管理器 没能获取到 一个 SqlSessionHolder 调用 sessionFactory.openSession() 新建一个SqlSession
  5. - 4 判断当前是否有事务,有则 根据 SqlSession 创建一个 SqlSessionHolder 并将其注册进入到 TransactionSynchronizationManager 中,以供当前事务中的下次使用
  6. 从上面的步骤中,我们发现整个获取SqlSession都与 **事务** 有极大的关联关系,并且从上面的流程中,我们能够得到几个关键点信息:
  7. - 1 同一事务中 不管调用多少次 mapper里的方法 ,最终都是用得同一个 sqlSession,即 一个事务中使用的是同一个sqlSession
  8. - 2 如果没有开启事务,调用一次mapper里的方法将会新建一个 sqlSession 来执行方法。
  9. 这个 **TransactionSynchronizationManager** 事务同步管理器 是由Spring 持有的,也就是说这里完美的应证了[Mybatis-Spring](http://mybatis.org/spring/zh/getting-started.html) 中对该子项目的描述:<br />MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。**它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession 并注入到 bean中**。
  10. > 成功获取到 SqlSession(准确的说是 DefaultSqlSession ) 通过 method.invoke() 反射调用到具体的 DefaultSqlSession 方法。方法调用完成后,判断当前SqlSession是否被事务所管控,如果是则不commit,最后再调用 closeSqlSession() 方法进行SqlSession “关闭”。这里为什么要打引号呢?原因是该方法内部处理时判断了当前SqlSession是否被事务所管控,是的话仅仅将引用计数器减一,并未真正将SqlSession 关闭(这也是为了下次能够使用同一个SqlSession),如果不被事务管控则执行正在的 session.close() 操作。
  11. <a name="zHkw1"></a>
  12. ### SqlSession实现数据库操作
  13. 任何的Mapper接口方法请求最终都会请求到 **DefaultSqlSession** ,即 **DefaultSqlSession** 内部封装了数据库操作,其他 SqlSession 子类最终都得依靠它来操作数据库。那么我们就拿 **DefaultSqlSession** 内部的 selectList() 方法开始讲述其如何封装了数据库操作。
  14. ```java
  15. public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  16. try {
  17. // 从 configuration 中获取到 指定方法的 MappedStatement (注意:statement 是由 MapperMethod 中的 SqlCommand 的 name 字段传下来的,而name 本身就来源于 MappedStatement 的 id ,所以最终 statement 会是 com.xxx.findUserByName 这种形式)
  18. MappedStatement ms = configuration.getMappedStatement(statement);
  19. // 通过 委托 Executor 的 query() 执行真正的数据库操作
  20. List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
  21. return result;
  22. } catch (Exception e) {
  23. throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
  24. } finally {
  25. ErrorContext.instance().reset();
  26. }
  27. }

我们可以发现 DefaultSqlSession 的所有方法 基本上都有以下2个步骤:

  • 1、 从 configuration 中获取到 指定方法的 MappedStatement
  • 2、 通过 委托 Executor 来 执行真正的数据库操作

我们知道 MappedStatement 内部保存了所要执行的方法的 SqlSource (保存有从Mapper.xml中解析出来的Sql片段信息),然后通过 Executor 的 query() 方法来执行数据库操作。我们先来看下 Executor 继承关系图:
image.png
可以看到 BaseExecutorCachingExecutor ,它们2个分别代表2中缓存机制, BaseExecutor 内部维护了 名为 localCache (PerpetualCache) 的 对象,该对象就是 一级缓存 的实际控制者, CachingExecutor 在二级缓存时使用,其内部实现时通过委托 BaseExecutor 来实现一级缓存的。
不管是一级缓存还是二级缓存机制,其最终还是会调用到 BaseExecutor,而Mybatis默认的 BaseExecutor 实现是 SimpleExecutor,所以重点关注这2个类的实现,下面是 BaseExecutor 的 query()源码:

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 从 MappedStatement 中获取到 BoundSql(实际上是通过 调用 MappedStatement 中的 SqlSource  的 getBoundSql() 获取)
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 通过参数 解析出 cacheKey ,这个是一级缓存的key 
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

我们可以看到整个方法内部最核心的一点就是 从 MappedStatement 中获取到 BoundSql(实际上是通过 调用 MappedStatement 中的 SqlSource 的 getBoundSql() 获取) ,最后再调用重载方法 query(),这个重发方法做了一级缓存的操作


public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
        // 从  MappedStatement 中获取到 Configuration 
        Configuration configuration = ms.getConfiguration();
        // 通过 Configuration 的 newStatementHandler() 方法创建了一个 StatementHandler 对象
        StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        // 调用 prepareStatement() 方法 获取到 Statement 对象 (真正执行静态SQl的接口)
        stmt = prepareStatement(handler, ms.getStatementLog());
        // 调用 StatementHandler.query() 方法执行
        return handler.<E>query(stmt, resultHandler);
    } finally {
        closeStatement(stmt);
    }
}

从这里开始就与JDBC 挂钩了,如果熟悉JDBC的同学应该一眼就知道 Statement 这个对象具体时干嘛的了,按照惯例我们还是先分析下 doQuery() 方法内部执行流程步骤:

  • 1、 从 MappedStatement 中获取到 Configuration
  • 2、 通过 Configuration 的 newStatementHandler() 方法创建了一个 StatementHandler 对象
  • 3、 调用 prepareStatement() 方法 获取到 Statement 对象 (真正执行静态SQl的接口)
  • 4、 调用 StatementHandler.query() 方法执行(其实内部委托 Statement 来执行的)

image.png
这个结构与Executor 的类似:

  • 1、 SimpleStatementHandler ,这个对应的 就是JDBC 中常用到的 Statement 接口,用于简单SQL的处理
  • 2、 PreparedStatementHandler , 这个对应的就是JDBC中的 PreparedStatement,用于预编译SQL的处理
  • 3、 CallableStatementHandler , 这个对应JDBC中 CallableStatement ,用于执行存储过程相关的处理
  • 4、 RoutingStatementHandler,这个接口是以上三个接口的路由,没有实际操作,只是负责上面三个StatementHandler的创建及调用

回过头来再看 configuration.newStatementHandler() ,不用猜测,肯定创建的是 RoutingStatementHandler ,并且其内部 的 delegate 默认是 PreparedStatementHandler (MappedStatement builder方法指定了默认的 statementType = StatementType.PREPARED )。
步骤三主要的作用就是 预编译并 获取到Statement(由于是 PreparedStatementHandler 所以默认获取到的是 PreparedStatement), 其 prepareStatement() 方法源码:

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    // 创建 Connection 链接,查看源码其是通过 transaction.getConnection() 获取到的。
    Connection connection = getConnection(statementLog);
    // 预编译获取到 PrepareStatement ,即 最终会调用到 connection.prepareStatement() 方法
    stmt = handler.prepare(connection);
    // 设置参数信息,其参数是通过 从 BoundSql 获取
    handler.parameterize(stmt);
    return stmt;
}

整个流程分3个步骤:

  • 1、 创建 Connection 链接,查看源码其是通过 transaction.getConnection() 获取到的。
  • 2、 预编译获取到 PrepareStatement ,即 最终会调用到 connection.prepareStatement() 方法
  • 3、 设置参数信息,其参数是通过 从 BoundSql 获取

步骤4会 调用 StatementHandler.query() 方法,以 PreparedStatementHandler 为例,其源码如下:


  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // 执行SQL
    ps.execute();
    // 通过 ResultSetHandler 的 handleResultSets() 方法解析返回数据
    return resultSetHandler.<E> handleResultSets(ps);
  }

这个方法内部就2个流程步骤:

  • 1、 执行SQL
  • 2、 通过 ResultSetHandler 的 handleResultSets() 方法解析返回数据

这里的 ResultSetHandler 就是用来处理 JDBC中的 ResultSet

SqlSession事务 的关系:

  • 1、 同一事务中 不管调用多少次 mapper里的方法 ,最终都是用得同一个 sqlSession,即 一个事务中使用的是同一个sqlSession。
  • 2、 如果没有开启事务,调用一次mapper里的方法将会新建一个 sqlSession 来执行方法。

SqlSession 的一次执行流程图:
image.png