mybatis 爲何每次插入的時候總會建立一個SqlSession?

問題記錄:


工做環境是使用spring boot,使用用的mybatis,在一次調試中。發現每一次插入一條
 數據都會建立一個SqlSession。如圖:

圖1:html

clipboard.png

問題可能的緣由:


緣由分析:#1 沒有使用緩存

由於這個是插入,不是查詢,因此這裏不存在什麼緩存的問題。

後來百度了一波,網上說是沒有使用事務。

加上@Transactional


圖2:spring

clipboard.png

發現「Creating a new SqlSession」這兩個煩人的東西竟然還在。

無論了,直接分析源碼

直接分析源碼,老子還不信了,搞不定你我還混什麼:
1.開啓debug
2.打上斷點

圖3:sql

clipboard.png

發現session爲空緩存

繼續走...

clipboard.png

圖2 #分析


public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
     //從從前線程的threadLocal 中獲取sqlSessionHolder
     SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
     //調用靜態方法sessionHoler 判斷是否存在符合要求的sqlSession
     SqlSession session = sessionHolder(executorType, holder);
     // 判斷當前sqlSessionHolder 中是否持有sqlSession (即當前操做是否在事務當中)
     if (session != null) {
         //若是持有sqlSesison 的引用,則直接獲取
         return session;
     }

     if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Creating a new SqlSession");
     }
    //獲取新的sqlSession 對象。這裏由sessionFacory產生的defaultSqlSession
     session = sessionFactory.openSession(executorType);
    //判斷判斷,當前是否存在事務,將sqlSession 綁定到sqlSessionHolder 中,並放到threadLoacl 當中
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
    return session;
}

圖3 #分析

private static SqlSession sessionHolder(ExecutorType executorType, SqlSessionHolder holder) {
     SqlSession session = null;
     if (holder != null && holder.isSynchronizedWithTransaction()) {
     //hodler保存的執行類型和獲取SqlSession的執行類型不一致,就會拋出異常,也就是說在同一個事務中,執行類型不能變化,緣由就是同一個事務中同一個sqlSessionFactory建立的sqlSession會被重用 
         if (holder.getExecutorType() != executorType) {
             throw new TransientDataAccessResourceException("Cannot change the ExecutorType when there is an existing transaction");
         }
             //增長該holder,也就是同一事務中同一個sqlSessionFactory建立的惟一sqlSession,其引用數增長,被使用的次數增長 
             holder.requested();

         if (LOGGER.isDebugEnabled()) {
             LOGGER.debug("Fetched SqlSession [" + holder.getSqlSession() + "] from current transaction");
         }
         //返回sqlSession 
         session = holder.getSqlSession();
    }
    return session;
}

固然,這裏還少了一個註冊的方法,貼上:

private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
        SqlSessionHolder holder; 
        //判斷事務是否存在
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            Environment environment =  sessionFactory.getConfiguration().getEnvironment();
            //加載環境變量,判斷註冊的事務管理器是不是SpringManagedTransaction,也就是Spring管理事務
        if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
             if (LOGGER.isDebugEnabled()) {
                 LOGGER.debug("Registering transaction synchronization for SqlSession [" + session + "]");
              }

             holder = new SqlSessionHolder(session, executorType, exceptionTranslator); 
             //若是當前回話處在事務當中,則將holder 綁定到ThreadLocal 中
             //以sessionFactory爲key,hodler爲value,加入到TransactionSynchronizationManager管理的本地緩存ThreadLocal<Map<Object, Object>> resources中 
              TransactionSynchronizationManager.bindResource(sessionFactory, holder);
              //將holder, sessionFactory的同步加入本地線程緩存中ThreadLocal<Set<TransactionSynchronization>> synchronizations 
              TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
              //設置當前holder和當前事務同步 
              holder.setSynchronizedWithTransaction(true);
              //holder 引用次數+1
             holder.requested();
          } else {
                if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because DataSource is not transactional");
                    }
                } else {
                       throw new TransientDataAccessResourceException( "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
                }
          }
} else {
  if (LOGGER.isDebugEnabled()) {
    LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because synchronization is not active");
  }
}

}

補充


  • sqlSessionHolder 繼承了spring的ResourceHolderSupportsession

    public abstract class ResourceHolderSupport implements ResourceHolder {
       //事務是否開啓private boolean synchronizedWithTransaction = false;    
       private boolean rollbackOnly = false;
       private Date deadline;
       // 引用次數
       private int referenceCount = 0;
       private boolean isVoid = false;
    }

  • 在sqlSession 關閉session 的時候, 使用了工具了sqlSessionUtils的closeSqlSession 方法。sqlSessionHolder 也是作了判斷,若是回話在事務當中,則減小引用次數,沒有真實關閉session。若是回話不存在事務,則直接關閉sessionmybatis

    public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
         notNull(session, NO_SQL_SESSION_SPECIFIED);
         notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    
         SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
         //若是holder 中持有sqlSession 的引用,(即會話存在事務)
         if ((holder != null) && (holder.getSqlSession() == session)) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Releasing transactional SqlSession [" + session + "]");
            } 
           //每當一個sqlSession 執行完畢,則減小holder 持有引用的次數
           holder.released();
           } else {
                  if (LOGGER.isDebugEnabled()) {
                      LOGGER.debug("Closing non transactional SqlSession [" + session + "]");
                  }
                  //若是回話中,不存在事務,則直接關閉session
                  session.close();
            }
    }

    到了這一步,問題已經很明顯了。工具


出現緣由 與 分析


  • SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory); 這一句:他從從前線程的threadLocal 中獲取sqlSessionHolder。可是在在sqlSession 關閉session 的時候,sqlSessionHolder也是作了判斷。若是會話在事務中,就減小引用次數,沒有真實關閉session。若是會話不存在事務,則直接關閉session。也就是說,必須開啓事務,但這個問題好像只是插入了一下,事務已經執行完成了,下一次插入的時候,因爲上一個事務執行完成了, 若是不存在holder或沒有被事務鎖定,則會建立新的sqlSession,即 Creating a new SqlSession,經過sessionFactory.openSession()方法。若是會話不存在事務,就直接把session關閉了,同時,也減小了引用次數。

  • 換一句話來講:若是在插入的代碼塊中,再加上一個查詢的代碼,或者再插入一條數據的代碼,這樣就不會出現Creating a new SqlSession這個煩人的傢伙。好了,祝你們好運!!!

引用:

SqlSessionHolder做用
若是有侵權,立刻刪除spa

相關文章
相關標籤/搜索