首先建立一個Maven project,在pom.xml中加入以下dependency
接著我們建立一個ProjectConfig的Class,
建立Domain Object - Employee
在我們的test資料庫中建立一個Employee的Table
為Employee建立一個repository,加上add方法insert 至資料庫
接下來我們來實作一個新增Employee的use case
並且為我們的use case 建立一個input的class
以及在ProjectConfin加上對應的宣告
@Transactional
如果我們沒有在execute方法上加上@Transactional的annotation
當程式即便丟出了RuntimeException,仍會將資料加入到資料庫中
當我們加上@Transactional的annotation,當發生RuntimeException,則會rollback,資料就不會新增到資料庫中
@Transactional 的rollbackFor屬性
這邊特別需要注意的是,如果annotation只有@Transactional
rollback the unchecked exception ( RuntimeExceptio, Error), but does not rollback the cheecked exception
在預設的情況下,transaction只會再發生unchecked exception的時候rollback, 但不會在發生cheecked exception的時候rollback
如果我們希望在發生cheecked exception的時候rollback,我們則可以在@Transactional 加上@Transactional(rollbackFor = Exception.class)
@Transactional 的noRollbackFor屬性
此外,如果你希望某個例外發生不要rollback,該如何做呢? 這時就可以利用 noRollbackFor 屬性,
例如: 我們希望再發生RuntimeException不要rollback,則可以這樣寫 noRollbackFor = RuntimeException.class
另外, 只有在public的method才可以使用transaction management
我們也可以把@Transactional的annotation放到Class的宣告上
如此表示該Class的所有public method都會配置相同的trancation屬性訊息
最後在我們的Main程式,執行一下我們的use case,測試是否有將資料加到資料庫中
接著到我們的資料庫中看看資料是否加進去了
@Transactional 的propagation屬性
接著我們要來看propagation屬性,
propagation定義了交易應用於方法上之邊界(Boundaries),它告知何時該開始一個新的交易,或何時交易該被暫停,或者方法是否要在交易中進行。
propagation屬性預設的狀況是REQUIRED
@Transactional(propagation = Propagation.REQUIRED) → 所以這樣寫其實跟預設的狀態是一樣的,不寫也沒關係
舉幾個例子來看
情境1
以上面這個例子來看,我們先在剛剛上面建立過的EmployeeRepository的add method上面也加上@Transactional
這個時候use case的execute方法呼叫了EmployeeRepository的add方法
只需要在呼叫execute方法時接到add方法中丟出來的RuntimeException,則transaction就會正常的rollback。
情境2
接著當我們的EmployeeRepository不變,在use case中我們在呼叫了EmployeeRepository的add方法
並且將add丟出來RuntimeException用try-catch接起來,則會出現如下方的exception
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
這是因為在use case和repository中的@Transactional propagation都採用預設值:REQUREID。
根據REQUIRED特性,當use case調用repository的時候,他們是處於同一個transaction中。
當repository中拋出了一個例外以後,repository會把當前的transaction標記為需要rollback。
但是use case中捕獲了這個例外,並進行了處理,認為當前transaction應該正常commit。
此時就出現了前後不一致,也就是因為這樣,才會丟出UnexpectedRollbackException。
情境3
接著我們的use case不變,修改repository中add方法的propagation為REQUIRES_NEW,如下:
此時我們的程式就可以正常的work,不會丟出UnexpectedRollbackException了。
原因是因為當use case調用repository時,repository的add是在一個新的transaction中執行的。
所以,當repository丟出例外以後,只會把新創建的transaction rollback了,而不會影響到use case的transaction。
use case就可以正常的進行commit。
@Transactional 的 isolation屬性
在一個應用程式中,可能有多個交易同時在進行,這些交易應當彼此之間互相不知道另一個交易的存在,
由於交易彼此之間獨立,若讀取的是同一個資料的話,就容易發生問題
Spring提供了幾種隔離層級設定,參考以下表格。預設採用DEFAULT
@Transactional 的 readOnly屬性
指定交易是否只進行讀取的動作,預設值是false。
像是一些getById, getAll的方法則可以設置@Transactional(readOnly=true)
@Transactional 的 timeout屬性
有的交易操作可能延續一段很長的時間,交易本身可能關聯到資料表格的鎖定,因而長時間的交易操作會有效能上的問題,
對於過長的交易操作,您要考慮回滾(Roll back)交易並要求重新操作,而不是無限時的等待交易完成。
預設狀態是-1,表示交易無限制時間。
由程式控制Transaction
使用 @Transactional 讓 spring framework 控制 transaction 相當方便,
但是如果今天有什麼特殊的原因需要讓程式控制transaction,該怎麼做?
例如,假設use case邏輯複雜到了一定程度,為了考量程式執行的performance
我不希望我的transaction boundary綁定了整個method,而是在存取repository時,才要開始transaction
因為transaction會跟資料庫鎖定有關係,我們只需要在存取資料庫的時後才做transaction
而其它的程式邏輯,就可以不用在transaction內,如此我們可以提升程式的效能
首先,我們在ProjectConfig先透過constructor injection,將PlatformTransactionManager注入到use case中
以取得 transaction 的管理權
接著在use case中,讓use case 自己管理transaction,而不透過 @Transactional
這裡只是一個example,說明該如何讓程式自己控制transaction
Reference:
https://sites.google.com/site/stevenattw/java/jdbc/spring-transaction-management
https://www.ibm.com/developerworks/cn/java/j-master-spring-transactional-use/index.html
https://openhome.cc/Gossip/SpringGossip/TransactionAttribute.html
https://www.itread01.com/content/1524114197.html
留言列表