原文 http://blog.didispace.com/springboottransactional/
什麼是事務?
我們在開發企業應用時,對於業務人員的一個操作實際是對數據讀寫的多步操作的結合。由於數據操作在順序執行的過程中,任何一步操作都有可能發生異常,異常會導致後續操作無法完成,此時由於業務邏輯並未正確的完成,之前成功操作數據的並不可靠,需要在這種情況下進行回退。
事務的作用就是爲了保證用戶的每一個操作都是可靠的,事務中的每一步操作都必須成功執行,只要有發生異常就回退到事務開始未進行操作的狀態。
事務管理是Spring框架中最爲常用的功能之一,我們在使用Spring Boot開發應用時,大部分情況下也都需要使用事務。
快速入門
在Spring Boot中,當我們使用了spring-boot-starter-jdbc或spring-boot-starter-data-jpa依賴的時候,框 架會自動默認分別注入DataSourceTransactionManager或JpaTransactionManager。所以我們不需要任何額外 配置就可以用@Transactional註解進行事務的使用。
在該樣例工程中(若對該數據訪問方式不瞭解,可先閱讀該文章),我們引入了spring-data-jpa,並創建了User實體以及對User的數據訪 問對象UserRepository,在ApplicationTest類中實現了使用UserRepository進行數據讀寫的單元測試用例,如下:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(Application.class)
public class ApplicationTests {
@Autowired
private UserRepository userRepository;
@Test
public void test() throws Exception {
// 創建10條記錄
userRepository.save(new User("AAA", 10));
userRepository.save(new User("BBB", 20));
userRepository.save(new User("CCC", 30));
userRepository.save(new User("DDD", 40));
userRepository.save(new User("EEE", 50));
userRepository.save(new User("FFF", 60));
userRepository.save(new User("GGG", 70));
userRepository.save(new User("HHH", 80));
userRepository.save(new User("III", 90));
userRepository.save(new User("JJJ", 100));
// 省略後續的一些驗證操作
}
}
可以看到,在這個單元測試用例中,使用UserRepository對象連續創建了10個User實體到數據庫中,下面我們人爲的來製造一些異常,看看會發生什麼情況。
通過定義User的name屬性長度爲5,這樣通過創建時User實體的name屬性超長就可以觸發異常產生。
@Entity
public class User {
@Id
@GeneratedValue
private Long id;
@Column(nullable = false, length = 5)
private String name;
@Column(nullable = false)
private Integer age;
// 省略構造函數、getter和setter
}
修改測試用例中創建記錄的語句,將一條記錄的name長度超過5,如下:name爲HHHHHHHHH的User對象將會拋出異常。
// 創建10條記錄
userRepository.save(new User("AAA", 10));
userRepository.save(new User("BBB", 20));
userRepository.save(new User("CCC", 30));
userRepository.save(new User("DDD", 40));
userRepository.save(new User("EEE", 50));
userRepository.save(new User("FFF", 60));
userRepository.save(new User("GGG", 70));
userRepository.save(new User("HHHHHHHHHH", 80));
userRepository.save(new User("III", 90));
userRepository.save(new User("JJJ", 100));
執行測試用例,可以看到控制檯中拋出瞭如下異常,name字段超長:
2016-05-27 10:30:35.948 WARN 2660 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 1406, SQLState: 22001
2016-05-27 10:30:35.948 ERROR 2660 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : Data truncation: Data too long for column 'name' at row 1
2016-05-27 10:30:35.951 WARN 2660 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Warning Code: 1406, SQLState: HY000
2016-05-27 10:30:35.951 WARN 2660 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : Data too long for column 'name' at row 1
org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; nested exception is org.hibernate.exception.DataException: could not execute statement
此時查數據庫中,創建了name從AAA到GGG的記錄,沒有HHHHHHHHHH、III、JJJ的記錄。而若這是一個希望保證完整性操作的情況 下,AAA到GGG的記錄希望能在發生異常的時候被回退,這時候就可以使用事務讓它實現回退,做法非常簡單,我們只需要在test函數上添加 @Transactional
註解即可。
@Test
@Transactional
public void test() throws Exception {
// 省略測試內容
}
這裏主要通過單元測試演示瞭如何使用 @Transactional
註解來聲明一個函數需要被事務管理,通常我們單元測試爲了保證每個測試之間的數據獨立,會使用 @Rollback
註解讓每個單元測試都能在結束時回滾。而真正在開發業務邏輯時,我們通常在service層接口中使用 @Transactional
來對各個業務邏輯進行事務管理的配置,例如:
public interface UserService {
@Transactional
User login(String name, String password);
}
事務詳解
上面的例子中我們使用了默認的事務配置,可以滿足一些基本的事務需求,但是當我們項目較大較複雜時(比如,有多個數據源等),這時候需要在聲明事務時,指定不同的事務管理器。對於不同數據源的事務管理配置可以見 《Spring Boot多數據源配置與使用》 中的設置。在聲明事務時,只需要通過value屬性指定配置的事務管理器名即可,例如: @Transactional(value="transactionManagerPrimary")
。
除了指定不同的事務管理器之後,還能對事務進行隔離級別和傳播行爲的控制,下面分別詳細解釋:
隔離級別
隔離級別是指若干個併發的事務之間的隔離程度,與我們開發時候主要相關的場景包括:髒讀取、重複讀、幻讀。
我們可以看 org.springframework.transaction.annotation.Isolation
枚舉類中定義了五個表示隔離級別的值:
public enum Isolation {
DEFAULT(-1),
READ_UNCOMMITTED(1),
READ_COMMITTED(2),
REPEATABLE_READ(4),
SERIALIZABLE(8);
}
DEFAULT
:這是默認值,表示使用底層數據庫的默認隔離級別。對大部分數據庫而言,通常這值就是:READ_COMMITTED
。READ_UNCOMMITTED
:該隔離級別表示一個事務可以讀取另一個事務修改但還沒有提交的數據。該級別不能防止髒讀和不可重複讀,因此很少使用該隔離級別。READ_COMMITTED
:該隔離級別表示一個事務只能讀取另一個事務已經提交的數據。該級別可以防止髒讀,這也是大多數情況下的推薦值。REPEATABLE_READ
:該隔離級別表示一個事務在整個過程中可以多次重複執行某個查詢,並且每次返回的記錄都相同。即使在多次查詢之間有新增的數據滿足該查詢,這些新增的記錄也會被忽略。該級別可以防止髒讀和不可重複讀。SERIALIZABLE
:所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止髒讀、不可重複讀以及幻讀。但是這將嚴重影響程序的性能。通常情況下也不會用到該級別。
指定方法:通過使用 isolation
屬性設置,例如:
@Transactional(isolation = Isolation.DEFAULT)
傳播行爲
所謂事務的傳播行爲是指,如果在開始當前事務之前,一個事務上下文已經存在,此時有若干選項可以指定一個事務性方法的執行行爲。
我們可以看 org.springframework.transaction.annotation.Propagation
枚舉類中定義了6個表示傳播行爲的枚舉值:
public enum Propagation {
REQUIRED(0),
SUPPORTS(1),
MANDATORY(2),
REQUIRES_NEW(3),
NOT_SUPPORTED(4),
NEVER(5),
NESTED(6);
}
REQUIRED
:如果當前存在事務,則加入該事務;如果當前沒有事務,則創建一個新的事務。SUPPORTS
:如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續運行。MANDATORY
:如果當前存在事務,則加入該事務;如果當前沒有事務,則拋出異常。REQUIRES_NEW
:創建一個新的事務,如果當前存在事務,則把當前事務掛起。NOT_SUPPORTED
:以非事務方式運行,如果當前存在事務,則把當前事務掛起。NEVER
:以非事務方式運行,如果當前存在事務,則拋出異常。NESTED
:如果當前存在事務,則創建一個事務作爲當前事務的嵌套事務來運行;如果當前沒有事務,則該取值等價於REQUIRED
。
指定方法:通過使用 propagation
屬性設置,例如:
@Transactional(propagation = Propagation.REQUIRED)