前言
最近項目有涉及到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());
}
}
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIn5GcuUTNwAzM0ATN00iNyIDNxMTM4ETMxgDM3EDMy0CN0ATM3ETMvwFOwcTMwIzLcRDNwEzNxEzLcd2bsJ2Lc12bj5ycn9Gbi52YucTMwIzcldWYtl2Lc9CX6MHc0RHaiojIsJye.png)
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:以非事務方式執行,如果目前存在事務,則抛出異常。
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());
}