Spring Cloud Spring Boot mybatis分佈式微服務雲架構(三十一)事務管理(1)

咱們在開發企業應用時,對於業務人員的一個操做實際是對數據讀寫的多步操做的結合。因爲數據操做在順序執行的過程當中,任何一步操做都有可能發生異常,異常會致使後續操做沒法完成,此時因爲業務邏輯並未正確的完成,以前成功操做數據的並不可靠,須要在這種狀況下進行回退。html

事務的做用就是爲了保證用戶的每個操做都是可靠的,事務中的每一步操做都必須成功執行,只要有發生異常就回退到事務開始未進行操做的狀態。git

事務管理是Spring框架中最爲經常使用的功能之一,咱們在使用Spring Boot開發應用時,大部分狀況下也都須要使用事務。spring

快速入門

在Spring Boot中,當咱們使用了spring-boot-starter-jdbc或spring-boot-starter-data-jpa依賴的時候,框架會自動默認分別注入DataSourceTransactionManager或JpaTransactionManager。因此咱們不須要任何額外配置就能夠用@Transactional註解進行事務的使用。數據庫

咱們以以前實現的《用spring-data-jpa訪問數據庫》的示例Chapter3-2-2做爲基礎工程進行事務的使用常識。springboot

在該樣例工程中(若對該數據訪問方式不瞭解,可先閱讀該文章),咱們引入了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屬性超長就能夠觸發異常產生。spring-boot

@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 {

    // 省略測試內容

}

再來執行該測試用例,能夠看到控制檯中輸出了回滾日誌(Rolled back transaction for test context),

2016-05-27 10:35:32.210  WARN 5672 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 1406, SQLState: 22001
2016-05-27 10:35:32.210 ERROR 5672 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : Data truncation: Data too long for column 'name' at row 1
2016-05-27 10:35:32.213  WARN 5672 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Warning Code: 1406, SQLState: HY000
2016-05-27 10:35:32.213  WARN 5672 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : Data too long for column 'name' at row 1
2016-05-27 10:35:32.221  INFO 5672 --- [           main] o.s.t.c.transaction.TransactionContext   : Rolled back transaction for test context [DefaultTestContext@1d7a715 testClass = ApplicationTests, testInstance = com.didispace.ApplicationTests@95a785, testMethod = test@ApplicationTests, testException = org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; nested exception is org.hibernate.exception.DataException: could not execute statement, mergedContextConfiguration = [MergedContextConfiguration@11f39f9 testClass = ApplicationTests, locations = '{}', classes = '{class com.didispace.Application}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{}', contextLoader = 'org.springframework.boot.test.SpringApplicationContextLoader', parent = [null]]].

org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; nested exception is org.hibernate.exception.DataException: could not execute statement

再看數據庫中,User表就沒有AAA到GGG的用戶數據了,成功實現了自動回滾。

這裏主要經過單元測試演示瞭如何使用@Transactional註解來聲明一個函數須要被事務管理,一般咱們單元測試爲了保證每一個測試之間的數據獨立,會使用@Rollback註解讓每一個單元測試都能在結束時回滾。而真正在開發業務邏輯時,咱們一般在service層接口中使用@Transactional來對各個業務邏輯進行事務管理的配置,例如:

public interface UserService {
    
    @Transactional
    User login(String name, String password);
    
}

源碼來源

相關文章
相關標籤/搜索