天天看點

mybatis:Creating a new SqlSession Closing non transactional SqlSession

Creating a new SqlSession
SqlSession [[email protected]] was not registered for synchronization because synchronization is not active
JDBC Connection [[email protected]] will not be managed by Spring
==>  Preparing: SELECT app_id, app_code, app_name FROM t_sys_application 
==> Parameters: 
<==    Columns: app_id, app_code, app_name
<==      Total: 2
Closing non transactional SqlSession [[email protected]]
           

在springboot配置了mybatis、Druid資料庫連接配接池後,發現每次sql執行mybatis都:

Creating a new SqlSession 
Closing non transactional SqlSession
           

以為資料庫連接配接池沒有生效,就去看了一下。直接上結論:mybatis的sqlSession和資料庫連接配接池中維護的資料庫連接配接Collection不是同一個概念,SqlSession是mybatis架構中的概念,是mybatis持久層架構的頂層API。在sqlSession中操作資料庫的時候會去擷取collection,collection的擷取是去連接配接池中取的!是以Creating a new SqlSession并不是每次都去建立了資料庫新連接配接,底層使用的collection還是連接配接池提供的。至于每次sql執行,mybatis都Creating a new SqlSession而不是共享SqlSession,是為了保證sql會話獨立避免發生髒資料,進而保證sql線程安全。

源碼随筆:

org.mybatis.spring.SqlSessionUtils.getSqlSession():

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

    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);

    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
      return session;
    }

    LOGGER.debug(() -> "Creating a new SqlSession");
    //擷取SqlSession
    session = sessionFactory.openSession(executorType);

    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
  }
           

sessionFactory.openSession(executorType)的實作:DefaultSqlSessionFactory.openSession()

@Override
  public SqlSession openSession(ExecutorType execType) {
    return openSessionFromDataSource(execType, null, false);
  }

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      //通過Confuguration對象去擷取Mybatis相關配置資訊, Environment對象包含了資料源和事務的配置
      // execType為執行器類型,配置檔案中定義
      // SimpleExecutor -- SIMPLE 就是普通的執行器。
      //ReuseExecutor -執行器會重用預處理語句(prepared statements)
      //BatchExecutor --它是批量執行器
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //定義執行器,是對statement的封裝
      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();
    }
  }
           

得到SqlSession對象之後就可以利用SqlSession提供的方法進行CRUD操作了。Connection對象是在SqlSession對象建立之後進行CURD操作中建立的。深入查找之後找到在ManagedTransaction類中找到擷取Connection對象的關鍵代碼如下:

protected void openConnection() throws SQLException {
    if (log.isDebugEnabled()) {
      log.debug("Opening JDBC Connection");
    }
    //dataSource 來源有三種,JndiDatasource,PooledDataSource,UnpooledDataSource,配置檔案中定義
    this.connection = this.dataSource.getConnection();
    if (this.level != null) {
      this.connection.setTransactionIsolation(this.level.getLevel());
    }
  }
           

PooledDataSource和UnPooledDataSource的差別是PooledDataSource使用了連接配接池。為什麼使用連接配接池呢?因為建立一個Connection對象的過程,在底層就相當于和資料庫建立的通信連接配接,在建立通信連接配接的過程,消耗了非常多的時間,而往往我們建立連接配接後(即建立Connection對象後),就執行一個簡單的SQL語句,然後就要抛棄掉,這是一個非常大的資源浪費!mybatis針對這一個問題提出的PooledDataSource使用了連接配接池。

補充:

在MyBatis中,多個SqlSession可以複用同一個Connection,同一個SqlSession中可以送出多個事務。

在service方法上添加@Transactional 開啟事務,把資料庫事務委托給spring管理,這樣多個sql執行就可以共用同一個SqlSession。

繼續閱讀