<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          嵌套事務(wù)、掛起事務(wù),Spring 事務(wù)機制有什么奧秘?

          共 5975字,需瀏覽 12分鐘

           ·

          2020-07-28 15:40

          Spring 做為風靡世界的Java 開源框架,發(fā)揮著舉足輕重的作用。那你有沒有想過, Spring 內(nèi)部又是怎么樣實現(xiàn)的事務(wù)呢?

          而且 在 Spring 之中除了設(shè)置事務(wù)的「隔離級別」之外,還可以額外配置事務(wù)的「傳播特性」。你要知道,傳播特性里,有兩個家伙比較特別,一個PROPAGATION_REQUIRES_NEW ,還有一個是PROPAGATION_NESTED。你要知道,所謂的 REQUIRES_NEW,是會在方法級聯(lián)調(diào)用的時候,會開啟一個新的事務(wù),同時掛起(suspend)當前事務(wù);NESTED 代表的嵌套事務(wù),則是在方法級聯(lián)調(diào)用的時候,在嵌套事務(wù)內(nèi)執(zhí)行。

          我們應(yīng)該都知道,這些傳播行為,是 Spring 獨有的,和數(shù)據(jù)庫沒有一毛錢關(guān)系。那在底層 Spring 用了什么黑魔法嗎?是怎么樣做到掛起事務(wù)的,又是怎么樣嵌套事務(wù)的呢?

          咱們一起來揭秘。


          毋庸置疑,數(shù)據(jù)的操作中,我們離不開事務(wù)。

          銀行的轉(zhuǎn)帳操作中,我們相信如果一邊扣款,那對方一定會收到,而不竹籃打水一場空

          事務(wù)在很多場景中都發(fā)揮著關(guān)鍵作用。

          咱們先以 MySQL 為例,來捋一捋數(shù)據(jù)庫的事務(wù),隔離級別。

          然后再來看這些數(shù)據(jù)庫的配置,特性,在 Spring 里是怎樣做的事務(wù)對應(yīng)的。

          以及 Spring 所謂的傳播特性,又是如何作用到數(shù)據(jù)庫的事務(wù)的,怎樣做到掛起事務(wù),嵌套事務(wù)。


          事務(wù)

          事務(wù)是什么?

          它是一級原子性的SQL操作,一個獨立的工作單元。如果工作單元中的SQL語句執(zhí)行成功,那會全部成功,一個失敗就會全部回滾。

          與數(shù)據(jù)庫的事務(wù)同時為大眾熟知的是ACID。即數(shù)據(jù)庫的原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)和持久性(Durability)。就像應(yīng)用內(nèi)為了線程之間的互斥,線程安全等,需要做大量的工作,數(shù)據(jù)庫為了 ACID,也做了許多的工作。


          隔離級別


          SQL 的標準中定義了四種隔離級別,規(guī)定了哪些是事務(wù)之間可見的,哪些是事務(wù)內(nèi)可見的。

          READ UNCOMMITTED (未提交讀) ?--> 兩個事務(wù)間,一個的事務(wù)還沒提交,但被另一個看到了。

          READ COMMITTED (提交讀) ? --> 兩個事務(wù)間,只有一個事務(wù)提交之后,另一個事務(wù)才能看到。

          REPEATABLE READ (可重復讀)--> 一個事務(wù)執(zhí)行中,多次讀到的結(jié)果是一樣的,即使其他事務(wù)做了修改,也先「看不見」

          SERIALIZABLE (可串行化)--> 最高隔離級別,強制事務(wù)串行執(zhí)行。

          由于這些隔離級別,會造成所謂的「臟讀」、「幻讀」、「不可重復讀」等問題,所以需要根據(jù)具體的場景選擇適用的。


          如何設(shè)置隔離級別


          對于 MySQL 的隔離級別,可以全局的,也可以設(shè)置 Session 級別的。

          官方文檔設(shè)置語法如下:

          我們通過客戶端連接到 MySQL Server 的時候,可以做一些配置,通過jdbc的 URL 你也能看的出來,這樣設(shè)置的就是 Session 級別的。

          參考上面的官方文檔,設(shè)置 session 隔離級別的命令是它:

          set session transaction isolation level SERIALIZABLE;


          注意,MySQL 默認的隔離級別是 REPEATABLE READ,我的改過。同時,這里查詢當前隔離級別的SQL,舊版本的是SELECT @@TX_ISOLATION;,新版本的是SELECT @@Transaction_ISOLATION; 注意區(qū)分。


          Spring 事務(wù)又是怎么回事


          看過了數(shù)據(jù)庫的隔離級別之后,我們再來看 Spring 里的實現(xiàn)。

          在 Spring 里,隔離級別的定義有五種,四個和數(shù)據(jù)庫的一致,另外包含一個 DEFAULT,代表不具體設(shè)置,直接使用數(shù)據(jù)庫定義的。

          咱們看到數(shù)據(jù)庫的隔離級別可以設(shè)置 GLOBAL 級別的,也可以設(shè)置 SESSION 級別的,每個 SESSION 則代表的是一個連接。而在Spring 內(nèi),我們又是通過一個個獨立的「連接」來操作數(shù)據(jù)庫,完成CRUD。到這兒咱們應(yīng)該就能知道,在 Spring 層面的設(shè)置,是通過 session 級別的 隔離來設(shè)置的,從而和數(shù)據(jù)庫里事務(wù)隔離級別做了對應(yīng)來實現(xiàn)。


          那傳播特性又是怎么回事呢?既然數(shù)據(jù)庫并不直接支持這樣的特性,Spring 又根據(jù)不同的傳播特性要求,來迂回實現(xiàn)了。

          總結(jié)起來,對于級聯(lián)操作中,如果是REQUIRED_NEW這種的情況,所謂的掛起當前事務(wù),開啟新的事務(wù),是 Spring 又去申請了一個新的 Connection,這樣就會對應(yīng)到一個新的事務(wù)上,然后將這個連接綁定到當前線程,再繼續(xù)執(zhí)行;


          對于嵌套事務(wù),底層則是通過數(shù)據(jù)庫的 SAVEPOINT 來實現(xiàn)所謂的「子事務(wù)」


          如果你熟悉代碼 DEBUG 過程中每個方法對應(yīng)的 Frame,可以類比一下,如果執(zhí)行失敗回滾的時候,可以指定回滾到當前事務(wù)的某個 SAVEPOINT,不需要全部回滾。


          在 Spring 層面,是通過 JDBC 3.0來實現(xiàn)的,看下面這段代碼注釋。

          * 

          This transaction manager supports nested transactions via the JDBC 3.0* {@link java.sql.Savepoint} mechanism. The* {@link #setNestedTransactionAllowed "nestedTransactionAllowed"} flag defaults* to "true", since nested transactions will work without restrictions on JDBC* drivers that support savepoints (such as the Oracle JDBC driver).


          事務(wù)掛起的部分代碼如下:

          /**   * Suspend the given transaction. Suspends transaction synchronization first,   * then delegates to the {@code doSuspend} template method.   * @param transaction the current transaction object   * (or {@code null} to just suspend active synchronizations, if any)   * @return an object that holds suspended resources   * (or {@code null} if neither transaction nor synchronization active)   * @see #doSuspend   * @see #resume   */  @Nullable  protected final SuspendedResourcesHolder suspend(@Nullable Object transaction) throws TransactionException {    if (TransactionSynchronizationManager.isSynchronizationActive()) {      List suspendedSynchronizations = doSuspendSynchronization();      try {        Object suspendedResources = null;        if (transaction != null) {          suspendedResources = doSuspend(transaction);        }        String name = TransactionSynchronizationManager.getCurrentTransactionName();        TransactionSynchronizationManager.setCurrentTransactionName(null);        boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();        TransactionSynchronizationManager.setCurrentTransactionReadOnly(false);        Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();        TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(null);        boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive();        TransactionSynchronizationManager.setActualTransactionActive(false);        return new SuspendedResourcesHolder(            suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel, wasActive);??????}  }


          邏輯在 doSuspend里,繼續(xù)看

            @Override  protected Object doSuspend(Object transaction) {    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;    txObject.setConnectionHolder(null);    return TransactionSynchronizationManager.unbindResource(obtainDataSource());  }
          @Override protected void doResume(@Nullable Object transaction, Object suspendedResources) { TransactionSynchronizationManager.bindResource(obtainDataSource(), suspendedResources); }


          然后對應(yīng)的bind 和?unbind 操作,是在ThreadLocal 對象里,將資源對象綁定或移出當前線程對應(yīng)的 resources 來實現(xiàn)的。

          private static final ThreadLocal> resources =      new NamedThreadLocal<>("Transactional resources");/**   * Bind the given resource for the given key to the current thread.   * @param key the key to bind the value to (usually the resource factory)   * @param value the value to bind (usually the active resource object)   * @throws IllegalStateException if there is already a value bound to the thread   * @see ResourceTransactionManager#getResourceFactory()   */  public static void bindResource(Object key, Object value) throws IllegalStateException {    Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);    Assert.notNull(value, "Value must not be null");    Map map = resources.get();    // set ThreadLocal Map if none found    if (map == null) {      map = new HashMap<>();      resources.set(map);    }    Object oldValue = map.put(actualKey, value);    // Transparently suppress a ResourceHolder that was marked as void...    if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {      oldValue = null;????}  }


          對比上面的說明和代碼,這兩個傳播特性的區(qū)別也很明了了:

          NESTED 的特性,本質(zhì)上還是同一個事務(wù)的不同保存點,如果涉及到外層事務(wù)回滾,則內(nèi)層的也將會被回滾;

          REQUIRED_NEW 的實現(xiàn)對應(yīng)的是一個新的事務(wù),拿到的是新的資源,所以外層事務(wù)回滾時,不影響內(nèi)層事務(wù)。



          點個在看,贊?支持我吧
          瀏覽 175
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  XX视频网 | 欧美操逼网址 | 国产淫秽视频免费 | 天天操人人操 | 国产精品久久久久久久久久久久久久久 |