真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

解析Mybatis的會話機制是什么-創(chuàng)新互聯(lián)

這篇文章主要介紹解析Mybatis的會話機制是什么,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!

站在用戶的角度思考問題,與客戶深入溝通,找到沐川網(wǎng)站設(shè)計與沐川網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗,讓設(shè)計與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個性化、用戶體驗好的作品,建站類型包括:網(wǎng)站設(shè)計、成都網(wǎng)站建設(shè)、企業(yè)官網(wǎng)、英文網(wǎng)站、手機端網(wǎng)站、網(wǎng)站推廣、國際域名空間、網(wǎng)頁空間、企業(yè)郵箱。業(yè)務(wù)覆蓋沐川地區(qū)。

坐在我旁邊的鐘同學(xué)聽說我精通Mybatis源碼(我就想不通,是誰透漏了風(fēng)聲),就順帶問了我一個問題:在同一個方法中,Mybatis多次請求數(shù)據(jù)庫,是否要創(chuàng)建多個SqlSession會話?

可能最近擼多了,當(dāng)時腦子里一片模糊,眼神迷離,雖然我當(dāng)時回答他:如果多個請求同一個事務(wù)中,那么多個請求都在共用一個SqlSession,反之每個請求都會創(chuàng)建一個SqlSession。這是我們在平常開發(fā)中都習(xí)以為常的常識了,但我卻沒有從原理的角度給鐘同學(xué)分析,導(dǎo)致鐘同學(xué)茶飯不思,作為老司機的我,感到深深的自責(zé),于是我暗自下定決心,要給鐘同學(xué)一個交代。

不服跑個demo

測試在方法中不加事務(wù)時,每個請求是否會創(chuàng)建一個SqlSession:

解析Mybatis的會話機制是什么

從日志可以看出,在沒有加事務(wù)的情況下,確實是Mapper的每次請求數(shù)據(jù)庫,都會創(chuàng)建一個SqlSession與數(shù)據(jù)庫交互,下面我們再看看加了事務(wù)的情況:

解析Mybatis的會話機制是什么

從日志可以看出,在方法中加了事務(wù)后,兩次請求只創(chuàng)建了一個SqlSession,再次證明了我上面的回答,但是僅僅這樣回答是體現(xiàn)完全不出一個老司機應(yīng)有的職業(yè)素養(yǎng)的,所以,我要發(fā)車了。

什么是SqlSession

在發(fā)車之前,我們必須得先搞明白,什么是SqlSession?

簡單來說,SqlSession是Mybatis工作的最頂層API會話接口,所有的數(shù)據(jù)庫操作都經(jīng)由它來實現(xiàn),由于它就是一個會話,即一個SqlSession應(yīng)該僅存活于一個業(yè)務(wù)請求中,也可以說一個SqlSession對應(yīng)這一次數(shù)據(jù)庫會話,它不是永久存活的,每次訪問數(shù)據(jù)庫時都需要創(chuàng)建它。

因此,SqlSession并不是線程安全,每個線程都應(yīng)該有它自己的 SqlSession 實例,千萬不能將一個SqlSession搞成單例形式,或者靜態(tài)域和實例變量的形式都會導(dǎo)致SqlSession出現(xiàn)事務(wù)問題,這也就是為什么多個請求同一個事務(wù)中會共用一個SqlSession會話的原因,我們從SqlSession的創(chuàng)建過程來說明這點:

  1. 從Configuration配置類中拿到Environment數(shù)據(jù)源;
  2. 從數(shù)據(jù)源中獲取TransactionFactory和DataSource,并創(chuàng)建一個Transaction連接管理對象;
  3. 創(chuàng)建Executor對象(SqlSession只是所有操作的門面,真正要干活的是Executor,它封裝了底層JDBC所有的操作細節(jié));
  4. 創(chuàng)建SqlSession會話。

每次創(chuàng)建一個SqlSession會話,都會伴隨創(chuàng)建一個專屬SqlSession的連接管理對象,如果SqlSession共享,就會出現(xiàn)事務(wù)問題。

從源碼的角度分析

源碼分析從哪一步作為入口呢?如果是看過我之前寫的那幾篇關(guān)于mybatis的源碼分析,我相信你不會在Mybatis源碼前磨磨蹭蹭,遲遲找不到入口。

在之前的文章里已經(jīng)說過了,Mapper的實現(xiàn)類是一個代理,真正執(zhí)行邏輯的是MapperProxy.invoke(),該方法最終執(zhí)行的是sqlSessionTemplate。

org.mybatis.spring.SqlSessionTemplate:

private final SqlSession sqlSessionProxy;

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
                          PersistenceExceptionTranslator exceptionTranslator) {

  notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
  notNull(executorType, "Property 'executorType' is required");

  this.sqlSessionFactory = sqlSessionFactory;
  this.executorType = executorType;
  this.exceptionTranslator = exceptionTranslator;
  this.sqlSessionProxy = (SqlSession) newProxyInstance(
    SqlSessionFactory.class.getClassLoader(),
    new Class[] { SqlSession.class },
    new SqlSessionInterceptor());
}

這個是創(chuàng)建SqlSessionTemplate的最終構(gòu)造方法,可以看出sqlSessionTemplate中用到了SqlSession,是SqlSessionInterceptor實現(xiàn)的一個動態(tài)代理類,所以我們直接深入要塞:

private class SqlSessionInterceptor implements InvocationHandler {
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    SqlSession sqlSession = getSqlSession(
      SqlSessionTemplate.this.sqlSessionFactory,
      SqlSessionTemplate.this.executorType,
      SqlSessionTemplate.this.exceptionTranslator);
    try {
      Object result = method.invoke(sqlSession, args);
      if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
        // force commit even on non-dirty sessions because some databases require
        // a commit/rollback before calling close()
        sqlSession.commit(true);
      }
      return result;
    } catch (Throwable t) {
      Throwable unwrapped = unwrapThrowable(t);
      if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
        // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        sqlSession = null;
        Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
        if (translated != null) {
          unwrapped = translated;
        }
      }
      throw unwrapped;
    } finally {
      if (sqlSession != null) {
        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
      }
    }
  }
}

Mapper所有的方法,最終都會用這個方法來處理所有的數(shù)據(jù)庫操作,茶飯不思的鐘同學(xué)眼神迷離不知道是不是自暴自棄導(dǎo)致擼多了,眼神空洞地望著我,問我spring整合mybatis和mybatis單獨使用是否有區(qū)別,其實沒區(qū)別,區(qū)別就是spring封裝了所有處理細節(jié),你就不用寫大量的冗余代碼,專注于業(yè)務(wù)開發(fā)。

該動態(tài)代理方法主要做了以下處理:

  1. 根據(jù)當(dāng)前條件獲取一個SqlSession,此時SqlSession可能是新創(chuàng)建的也有可能是獲取到上一次請求的SqlSession;
  2. 反射執(zhí)行SqlSession方法,再判斷當(dāng)前會話是否是一個事務(wù),如果是一個事務(wù),則不commit;
  3. 如果此時拋出異常,判斷如果是PersistenceExceptionTranslator且不為空,那么就關(guān)閉當(dāng)前會話,并且將sqlSession置為空防止finally重復(fù)關(guān)閉,PersistenceExceptionTranslator是spring定義的數(shù)據(jù)訪問集成層的異常接口;
  4. finally無論怎么執(zhí)行結(jié)果如何,只要當(dāng)前會話不為空,那么就會執(zhí)行關(guān)閉當(dāng)前會話操作,關(guān)閉當(dāng)前會話操作又會根據(jù)當(dāng)前會話是否有事務(wù)來決定會話是釋放還是直接關(guān)閉。

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;
  }

  if (LOGGER.isDebugEnabled()) {
    LOGGER.debug("Creating a new SqlSession");
  }

  session = sessionFactory.openSession(executorType);

  registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

  return session;
}

是不是看到了不服跑個demo時看到的日志“Creating a new SqlSession”了,那么證明我直接深入的地方挺準確的,沒有絲毫誤差。在這個方法當(dāng)中,首先是從TransactionSynchronizationManager(以下稱當(dāng)前線程事務(wù)管理器)獲取當(dāng)前線程threadLocal是否有SqlSessionHolder,如果有就從SqlSessionHolder取出當(dāng)前SqlSession,如果當(dāng)前線程threadLocal沒有SqlSessionHolder,就從sessionFactory中創(chuàng)建一個SqlSession,具體的創(chuàng)建步驟上面已經(jīng)說過了,接著注冊會話到當(dāng)前線程threadLocal中。

先來看看當(dāng)前線程事務(wù)管理器的結(jié)構(gòu):

public abstract class TransactionSynchronizationManager {
  // ...
  // 存儲當(dāng)前線程事務(wù)資源,比如Connection、session等
  private static final ThreadLocal> resources =
    new NamedThreadLocal<>("Transactional resources");
  // 存儲當(dāng)前線程事務(wù)同步回調(diào)器
  // 當(dāng)有事務(wù),該字段會被初始化,即激活當(dāng)前線程事務(wù)管理器
  private static final ThreadLocal> synchronizations =
    new NamedThreadLocal<>("Transaction synchronizations");
  // ...
}

這是spring的一個當(dāng)前線程事務(wù)管理器,它允許將當(dāng)前資源存儲到當(dāng)前線程ThreadLocal中,從前面也可看出SqlSessionHolder是保存在resources中。

org.mybatis.spring.SqlSessionUtils#registerSessionHolder:

private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
                                          PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
  SqlSessionHolder holder;
  // 判斷當(dāng)前是否有事務(wù)
  if (TransactionSynchronizationManager.isSynchronizationActive()) {
    Environment environment = sessionFactory.getConfiguration().getEnvironment();
    // 判斷當(dāng)前環(huán)境配置的事務(wù)管理工廠是否是SpringManagedTransactionFactory(默認)
    if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Registering transaction synchronization for SqlSession [" + session + "]");
      }

      holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
      // 綁定當(dāng)前SqlSessionHolder到線程ThreadLocal中
      TransactionSynchronizationManager.bindResource(sessionFactory, holder);
      // 注冊SqlSession同步回調(diào)器
      TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
      holder.setSynchronizedWithTransaction(true);
      // 會話使用次數(shù)+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");
    }
  }
}

注冊SqlSession到當(dāng)前線程事務(wù)管理器的條件首先是當(dāng)前環(huán)境中有事務(wù),否則不注冊,判斷是否有事務(wù)的條件是synchronizations的ThreadLocal是否為空:

public static boolean isSynchronizationActive() {
  return (synchronizations.get() != null);
}

每當(dāng)我們開啟一個事務(wù),會調(diào)用initSynchronization()方法進行初始化synchronizations,以激活當(dāng)前線程事務(wù)管理器。

public static void initSynchronization() throws IllegalStateException {
  if (isSynchronizationActive()) {
    throw new IllegalStateException("Cannot activate transaction synchronization - already active");
  }
  logger.trace("Initializing transaction synchronization");
  synchronizations.set(new LinkedHashSet());
}

所以當(dāng)前有事務(wù)時,會注冊SqlSession到當(dāng)前線程ThreadLocal中。

Mybatis自己也實現(xiàn)了一個自定義的事務(wù)同步回調(diào)器SqlSessionSynchronization,在注冊SqlSession的同時,也會將SqlSessionSynchronization注冊到當(dāng)前線程事務(wù)管理器中,它的作用是根據(jù)事務(wù)的完成狀態(tài)回調(diào)來處理線程資源,即當(dāng)前如果有事務(wù),那么當(dāng)每次狀態(tài)發(fā)生時就會回調(diào)事務(wù)同步器,具體細節(jié)可移步至Spring的org.springframework.transaction.support包。

回到SqlSessionInterceptor代理類的邏輯,發(fā)現(xiàn)判斷會話是否需要提交要調(diào)用以下方法:

org.mybatis.spring.SqlSessionUtils#isSqlSessionTransactional:

public static boolean isSqlSessionTransactional(SqlSession session, SqlSessionFactory sessionFactory) {
  notNull(session, NO_SQL_SESSION_SPECIFIED);
  notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);

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

  return (holder != null) && (holder.getSqlSession() == session);
}

取決于當(dāng)前SqlSession是否為空并且判斷當(dāng)前SqlSession是否與ThreadLocal中的SqlSession相等,前面也分析了,如果當(dāng)前沒有事務(wù),SqlSession是不會保存到事務(wù)同步管理器的,即沒有事務(wù),會話提交。

org.mybatis.spring.SqlSessionUtils#closeSqlSession:

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);
  if ((holder != null) && (holder.getSqlSession() == session)) {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Releasing transactional SqlSession [" + session + "]");
    }
    holder.released();
  } else {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Closing non transactional SqlSession [" + session + "]");
    }
    session.close();
  }
}

方法無論執(zhí)行結(jié)果如何都需要執(zhí)行關(guān)閉會話邏輯,這里的判斷也是判斷當(dāng)前是否有事務(wù),如果SqlSession在事務(wù)當(dāng)中,則減少引用次數(shù),沒有真實關(guān)閉會話。如果當(dāng)前會話不存在事務(wù),則直接關(guān)閉會話。

寫在最后

雖說鐘同學(xué)問了我一個Mybatis的問題,我卻中了Spring的圈套,猛然發(fā)現(xiàn)整個事務(wù)鏈路都處在Spring的管控當(dāng)中,這里涉及到了Spring的自定義事務(wù)的一些機制,其中當(dāng)前線程事務(wù)管理器是整個事務(wù)的核心與中軸,當(dāng)前有事務(wù)時,會初始化當(dāng)前線程事務(wù)管理器的synchronizations,即激活了當(dāng)前線程同步管理器,當(dāng)Mybatis訪問數(shù)據(jù)庫會首先從當(dāng)前線程事務(wù)管理器獲取SqlSession,如果不存在就會創(chuàng)建一個會話,接著注冊會話到當(dāng)前線程事務(wù)管理器中,如果當(dāng)前有事務(wù),則會話不關(guān)閉也不commit,Mybatis還自定義了一個TransactionSynchronization,用于事務(wù)每次狀態(tài)發(fā)生時回調(diào)處理。

以上是解析Mybatis的會話機制是什么的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對大家有幫助,更多相關(guān)知識,歡迎關(guān)注創(chuàng)新互聯(lián)成都網(wǎng)站設(shè)計公司行業(yè)資訊頻道!

另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機、免備案服務(wù)器”等云主機租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價比高”等特點與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。


標題名稱:解析Mybatis的會話機制是什么-創(chuàng)新互聯(lián)
轉(zhuǎn)載來于:http://weahome.cn/article/gjscd.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部