天天看點

Spring事務的傳播行為分析

前言

最近項目有涉及到Spring事務,是以工作之餘,想認真了解學習下Spring事務,查閱了若幹資料,做了一個demo(PS:參考了大牛的)。

現分享總結如下:

1、Spring 事務的簡介

了解事務之前,

先講一個你日常生活中最常幹的事:取錢。 

比如你去ATM機取1000塊錢,大體有兩個步驟:首先輸入密碼金額,銀行卡扣掉1000元錢;然後ATM出1000元錢。這兩個步驟必須是要麼都執行要麼都不執行。如果銀行卡扣除了1000塊但是ATM出錢失敗的話,你将會損失1000元;如果銀行卡扣錢失敗但是ATM卻出了1000塊,那麼銀行将損失1000元。是以,如果一個步驟成功另一個步驟失敗對雙方都不是好事,如果不管哪一個步驟失敗了以後,整個取錢過程都能復原,也就是完全取消所有操作的話,這對雙方都是極好的。 

事務就是用來解決類似問題的。事務是一系列的動作,它們綜合在一起才是一個完整的工作單元,這些動作必須全部完成,如果有一個失敗的話,那麼事務就會復原到最開始的狀态,仿佛什麼都沒發生過一樣。 

在企業級應用程式開發中,事務管理必不可少的技術,用來確定資料的完整性和一緻性。 

事務有四個特性:ACID

  • 原子性(Atomicity):事務是一個原子操作,由一系列動作組成。事務的原子性確定動作要麼全部完成,要麼完全不起作用。
  • 一緻性(Consistency):一旦事務完成(不管成功還是失敗),系統必須確定它所模組化的業務處于一緻的狀态,而不會是部分完成部分失敗。在現實中的資料不應該被破壞。
  • 隔離性(Isolation):可能有許多事務會同時處理相同的資料,是以每個事務都應該與其他事務隔離開來,防止資料損壞。
  • 持久性(Durability):一旦事務完成,無論發生什麼系統錯誤,它的結果都不應該受到影響,這樣就能從任何系統崩潰中恢複過來。通常情況下,事務的結果被寫到持久化存儲器中。

 具體可以參考:http://www.mamicode.com/info-detail-1248286.html

2、事務的傳播行為

事務的第一個方面是傳播行為(propagation behavior)。當事務方法被另一個事務方法調用時,必須指定事務應該如何傳播。例如:方法可能繼續在現有事務中運作,也可能開啟一個新事務,并在自己的事務中運作。Spring定義了七種傳播行為:

1、PROPAGATION_REQUIRED:如果目前沒有事務,就建立一個新事務,如果目前存在事務,就加入該事務,該設定是最常用的設定。

2、PROPAGATION_SUPPORTS:支援目前事務,如果目前存在事務,就加入該事務,如果目前不存在事務,就以非事務執行。‘

3、PROPAGATION_MANDATORY:支援目前事務,如果目前存在事務,就加入該事務,如果目前不存在事務,就抛出異常。

4、PROPAGATION_REQUIRES_NEW:建立新事務,無論目前存不存在事務,都建立新事務。

5、PROPAGATION_NOT_SUPPORTED:以非事務方式執行操作,如果目前存在事務,就把目前事務挂起。

6、PROPAGATION_NEVER:以非事務方式執行,如果目前存在事務,則抛出異常。

7、PROPAGATION_NESTED:如果目前存在事務,則在嵌套事務内執行。如果目前沒有事務,則執行與PROPAGATION_REQUIRED類似的操作。

下面我們來看下代碼:

3、demo分析

事先插入一條記錄

@Override
    public void before() {
        jdbcTemplate.update("INSERT  INTO USER (name,password) VALUES (?,?)","xiang","11111112");
    }      

  3.1、PROPAGATION_REQUIRED:如果目前沒有事務,就建立一個新事務,如果目前存在事務,就加入該事務,該設定是最常用的設定。

@Override
    public void txRollbackInnerTxRollbackPropagationRequires() {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                jdbcTemplate.update("INSERT INTO  USER (name,password) VALUES (?,?)","Huang","1111231");
                transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                    @Override
                    protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                        jdbcTemplate.update("insert into user name, password) values (?, ?)",
                                "Huang", "1111112");
                        //内部事務設定了 setRollbackOnly
                        transactionStatus.setRollbackOnly();
                    }
                });
            }
        });
    }      

測試結果:

/**
     * PROPAGATION_REQUIRES:内部事務設定了 {@link org.springframework.transaction.TransactionStatus#setRollbackOnly()} 來觸發復原,
     * 外部事務接受到了一個 {@link UnexpectedRollbackException} 也被復原
     */
    @Test
    public void testTxRollbackInnerTxRollbackPropagationRequires() throws Exception {
        try {
            springTxService.txRollbackInnerTxRollbackPropagationRequires();
        } catch (UnexpectedRollbackException e) {

        }finally {
            Assert.assertEquals(1,springTxService.mysqlConnection());
        }
    }      
Spring事務的傳播行為分析

3.2、PROPAGATION_SUPPORTS:支援目前事務,如果目前存在事務,就加入該事務,如果目前不存在事務,就以非事務執行。

@Override
    public void txRollbackInnerTxRollbackPropagationSupports() {
        supportsTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
                        "1111112");
                throw new CustomRuntimeException();
            }
        });
    }

    @Override
    public void txRollbackInnerTxRollbackPropagationSupports2() {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
                        "1111112");
                supportsTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
                    @Override
                    protected void doInTransactionWithoutResult(TransactionStatus status) {
                        jdbcTemplate.update("insert into user (name, password) values (?, ?)",
                                "Huang", "1111112");
                        status.setRollbackOnly();
                    }
                });
            }
        });
    }      

測試用例:

/**
     * PROPAGATION_SUPPORTS:如果目前事務上下文中沒有事務,
     * 那麼就按照沒有事務的方式執行代碼
     */
    @Test
    public void testTxRollbackInnerTxRollbackPropagationSupports() throws Exception {
        try {
            springTxService.txRollbackInnerTxRollbackPropagationSupports();
        } catch (CustomRuntimeException e) {
            e.printStackTrace();
        }finally {
            Assert.assertEquals(2, springTxService.mysqlConnection());

        }

    }
    /**
     * PROPAGATION_SUPPORTS:如果目前事務上下文中存在事務,
     * 那麼合并到目前上下文的事務中去,
     * 表現地和 {@link org.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRED} 一樣
     */
    @Test(expected = UnexpectedRollbackException.class)
    public void testTxRollbackInnerTxRollbackPropagationSupports2() throws Exception {
        springTxService.txRollbackInnerTxRollbackPropagationSupports2();
    }      

3.3、PROPAGATION_MANDATORY:支援目前事務,如果目前存在事務,就加入該事務,如果目前不存在事務,就抛出異常。

@Override
    public void txRollbackInnerTxRollbackPropagationMandatory() {
        mandatoryTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
                        "1111112");
            }
        });
    }

    @Override
    public void txRollbackInnerTxRollbackPropagationMandatory2() {
        nestedTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
                        "1111112");
                mandatoryTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
                    @Override
                    protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                        jdbcTemplate.update("insert into user (name, password) values (?, ?)",
                                "Huang", "1111112");
                        //内部事務復原了,外部事務也跟着復原
                        transactionStatus.setRollbackOnly();
                    }
                });
            }
        });
    }      
/**
     * PROPAGATION_MANDATORY:強制性的事務,目前的事務上下文中不存在事務的話,會抛出 {@link IllegalTransactionStateException}
     */
    @Test(expected = IllegalTransactionStateException.class)
    public void testTxRollbackInnerTxRollbackPropagationMandatory() throws Exception {
        springTxService.txRollbackInnerTxRollbackPropagationMandatory();

    }
    /**
     * PROPAGATION_MANDATORY:強制性的事務,内部的事務發生復原,
     * 那麼外部的事務也會發生復原,表現地和 {@link org.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRED}
     * 一樣,也會抛出 {@link UnexpectedRollbackException}
     */
    @Test(expected = UnexpectedRollbackException.class)
    public void testTxRollbackInnerTxRollbackPropagationMandatory2() throws Exception {
        springTxService.txRollbackInnerTxRollbackPropagationMandatory2();
    }      

3.4、PROPAGATION_REQUIRES_NEW:建立新事務,無論目前存不存在事務,都建立新事務。

@Override
    public void txRollbackInnerTxRollbackPropagationRequiresNew() {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                requiresNewTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
                    @Override
                    protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                        jdbcTemplate.update("insert into user (name, password) values (?, ?)",
                                "Huang", "1111112");
                    }
                });
                //外部事務發生復原,内部事務應該不受影響還是能夠送出
                throw new RuntimeException();
            }
        });
    }

    @Override
    public void txRollbackInnerTxRollbackPropagationRequiresNew2() {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
                        "1111112");
                requiresNewTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
                    @Override
                    protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                        jdbcTemplate.update("insert into user (name, password) values (?, ?)",
                                "Huang", "1111112");
                        // 内部事務發生復原,但是外部事務不應該發生復原
                        transactionStatus.setRollbackOnly();
                    }
                });
            }
        });
    }

    @Override
    public void txRollbackInnerTxRollbackPropagationRequiresNew3() {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
                        "1111112");
                requiresNewTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
                    @Override
                    protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                        jdbcTemplate.update("insert into user (name, password) values (?, ?)",
                                "Huang", "1111112");
                        // 内部事務抛出 RuntimeException,外部事務接收到異常,依舊會發生復原
                        throw new RuntimeException();
                    }
                });
            }
        });
    }      
/**
     * PROPAGATION_REQUIRES_NEW:外部事務發生復原,内部事務繼續送出,不受影響
     */
    @Test
    public void testTxRollbackInnerTxRollbackPropagationRequiresNew() throws Exception {
        try {
            springTxService.txRollbackInnerTxRollbackPropagationRequiresNew();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            Assert.assertEquals(2,springTxService.mysqlConnection());
        }
    }
    /**
     * PROPAGATION_REQUIRES_NEW:内部事務通過設定 {@link org.springframework.transaction.TransactionStatus#setRollbackOnly()} 來觸發復原,
     * 外部事務依舊可以不受影響,正常送出
     */
    @Test
    public void testTxRollbackInnerTxRollbackPropagationRequiresNew2() throws Exception {
        springTxService.txRollbackInnerTxRollbackPropagationRequiresNew2();
        Assert.assertEquals(2,springTxService.mysqlConnection());
    }
    /**
     * PROPAGATION_REQUIRES_NEW:内部事務抛出了 {@link RuntimeException} 異常發生了復原,外部事務接收到這個異常也會發生復原
     */
    @Test
    public void testTxRollbackInnerTxRollbackPropagationRequiresNew3() throws Exception {
        try {
            springTxService.txRollbackInnerTxRollbackPropagationRequiresNew3();
        } catch (RuntimeException e) {
            e.printStackTrace();
        }finally {
            Assert.assertEquals(1,springTxService.mysqlConnection());

        }
    }      

3.5、PROPAGATION_NOT_SUPPORTED:以非事務方式執行操作,如果目前存在事務,就把目前事務挂起。

@Override
    public void txRollbackInnerTxRollbackPropagationNotSupport() {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
                        "1111112");
                notSupportedTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
                    @Override
                    protected void doInTransactionWithoutResult(TransactionStatus status) {
                        jdbcTemplate.update("insert into user (name, password) values (?, ?)",
                                "Huang", "1111112");
                    }
                });
                // 外部事務復原,不會把内部的也連着復原
                transactionStatus.setRollbackOnly();
            }
        });
    }

    @Override
    public void txRollbackInnerTxRollbackPropagationNotSupport2() {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                try {
                    notSupportedTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
                        @Override
                        protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                            jdbcTemplate.update("insert into user (name, password) values (?, ?)",
                                    "Huang", "1111112");
                            throw new CustomRuntimeException();
                        }
                    });
                } catch (CustomRuntimeException e) {
                }
            }
        });
    }      
/**
     * PROPAGATION_NOT_SUPPORTED:不支援事務,
     * 外圍的事務復原不會導緻它包含的内容復原
     */
    @Test
    public void testTxRollbackInnerTxRollbackPropagationNotSupport() throws Exception {
        springTxService.txRollbackInnerTxRollbackPropagationNotSupport();
        Assert.assertEquals(2,springTxService.mysqlConnection());

    }
    /**
     * PROPAGATION_NOT_SUPPORTED:不支援事務,内部發生異常,外部捕獲,都不會發生復原
     */
    @Test
    public void testTxRollbackInnerTxRollbackPropagationNotSupport2() throws Exception {
        springTxService.txRollbackInnerTxRollbackPropagationNotSupport2();
        Assert.assertEquals(3,springTxService.mysqlConnection());
    }      

3.6、PROPAGATION_NEVER:以非事務方式執行,如果目前存在事務,則抛出異常。

Spring事務的傳播行為分析
Spring事務的傳播行為分析
1   @Override
 2     public void txRollbackInnerTxRollbackPropagationNever() {
 3         neverTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
 4             @Override
 5             protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
 6                 jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
 7                         "1111112");
 8             }
 9         });
10     }
11 
12     @Override
13     public void txRollbackInnerTxRollbackPropagationNever2() {
14         transactionTemplate.execute(new TransactionCallbackWithoutResult() {
15             @Override
16             protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
17                 jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
18                         "1111112");
19                 neverTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
20                     @Override
21                     protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
22                         jdbcTemplate.update("insert into user (name, password) values (?, ?)",
23                                 "Huang", "1111112");
24                     }
25                 });
26             }
27         });
28     }
29 
30     @Override
31     public void txRollbackInnerTxRollbackPropagationNever3() {
32         neverTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
33             @Override
34             protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
35                 jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
36                         "1111112");
37                 neverTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
38                     @Override
39                     protected void doInTransactionWithoutResult(TransactionStatus status) {
40                         jdbcTemplate.update("insert into user (name, password) values (?, ?)",
41                                 "Huang", "1111112");
42                     }
43                 });
44             }
45         });
46     }      

View Code

測試代碼:

/**
     * PROPAGATION_NEVER:不允許目前事務上下文中存在事務,如果沒有,就正常執行
     */
    @Test
    public void testTxRollbackInnerTxRollbackPropagationNever() throws Exception {
        springTxService.txRollbackInnerTxRollbackPropagationNever();
        Assert.assertEquals(2,springTxService.mysqlConnection());
    }
    /**
     * PROPAGATION_NEVER:不允許目前事務上下文中存在事務,
     * 如果有,則抛出 {@link IllegalTransactionStateException}
     */
    @Test(expected = IllegalTransactionStateException.class)
    public void testTxRollbackInnerTxRollbackPropagationNever2() throws Exception {
        springTxService.txRollbackInnerTxRollbackPropagationNever2();
    }
    /**
     * PROPAGATION_NEVER:不允許目前事務上下文中存在事務,
     * 當兩個 NEVER 的嵌套在一起的時候,應該也是能夠執行成功的。
     */
    @Test
    public void testTxRollbackInnerTxRollbackPropagationNever3() throws Exception {
        springTxService.txRollbackInnerTxRollbackPropagationNever3();
        Assert.assertEquals(3,springTxService.mysqlConnection());
    }      

3.7、PROPAGATION_NESTED:如果目前存在事務,則在嵌套事務内執行。如果目前沒有事務,則執行與PROPAGATION_REQUIRED類似的操作。

@Override
    public void txRollbackInnerTxRollbackPropagationNested() {
        nestedTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
                        "1111112");
                nestedTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
                    @Override
                    protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                        jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
                                "1111112");
                        // 内部事務設定了 rollbackOnly,外部事務應該不受影響,可以繼續送出
                        transactionStatus.setRollbackOnly();
                    }
                });
            }
        });
    }

    @Override
    public void txRollbackInnerTxRollbackPropagationNested2() {
        nestedTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
                        "1111112");
                nestedTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
                    @Override
                    protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                        jdbcTemplate.update("insert into user (name, password) values (?, ?)",
                                "Huang", "1111112");
                    }
                });
                // 外部事務設定了 rollbackOnly,内部事務應該也被復原掉
                transactionStatus.setRollbackOnly();
            }
        });
    }      
/**
     * PROPAGATION_NESTED:内部事務通過設定 {@link org.springframework.transaction.TransactionStatus#setRollbackOnly()} 來觸發復原,
     * 外部事務依舊可以不受影響,正常送出
     */
    @Test
    public void testTxRollbackInnerTxRollbackPropagationNested() throws Exception {
        springTxService.txRollbackInnerTxRollbackPropagationNested();
        Assert.assertEquals(2, springTxService.mysqlConnection());

    }
    /**
     * PROPAGATION_NESTED:外部事務通過設定 {@link org.springframework.transaction.TransactionStatus#setRollbackOnly()} 來觸發復原,由于
     * savepoint 在外部事務的開頭,是以内部事務應該也會被一起復原掉
     */
    @Test
    public void testTxRollbackInnerTxRollbackPropagationNested2() throws Exception {
        springTxService.txRollbackInnerTxRollbackPropagationNested2();
        Assert.assertEquals(1, springTxService.mysqlConnection());

    }      

繼續閱讀