Spring Boot中的事務管理

負載均衡和容錯,限流,降級。這些。
css

什麼是事務?

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

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

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

快速入門

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

在該樣例工程中(若對該數據訪問方式不瞭解,可先閱讀該文章),咱們引入了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實體到數據庫中,下面咱們人爲的來製造一些異常,看看會發生什麼狀況。springboot

經過定義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 來對各個業務邏輯進行事務管理的配置,例如:

=====================================================
SpringBoot事物的使用public interface UserService { @Transactional User login(String name, String password); }

spring Boot 使用事務很是簡單,首先使用註解 @EnableTransactionManagement 開啓事務支持後,而後在訪問數據庫的Service方法上添加註解 @Transactional 即可。

關於事務管理器,不論是JPA仍是JDBC等都實現自接口 PlatformTransactionManager 若是你添加的是 spring-boot-starter-jdbc 依賴,框架會默認注入 DataSourceTransactionManager 實例。若是你添加的是 spring-boot-starter-data-jpa 依賴,框架會默認注入 JpaTransactionManager 實例。

你能夠在啓動類中添加以下方法,Debug測試,就能知道自動注入的是 PlatformTransactionManager 接口的哪一個實現類。

@EnableTransactionManagement // 啓註解事務管理,等同於xml配置方式的 <tx:annotation-driven /> @SpringBootApplication public class ProfiledemoApplication { @Bean public Object testBean(PlatformTransactionManager platformTransactionManager){ System.out.println(">>>>>>>>>>" + platformTransactionManager.getClass().getName()); return new Object(); } public static void main(String[] args) { SpringApplication.run(ProfiledemoApplication.class, args); } }

 

 

這些SpringBoot爲咱們自動作了,這些對咱們並不透明,若是你項目作的比較大,添加的持久化依賴比較多,咱們仍是會選擇人爲的指定使用哪一個事務管理器。 
代碼以下:

@EnableTransactionManagement @SpringBootApplication public class ProfiledemoApplication { // 其中 dataSource 框架會自動爲咱們注入 @Bean public PlatformTransactionManager txManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean public Object testBean(PlatformTransactionManager platformTransactionManager) { System.out.println(">>>>>>>>>>" + platformTransactionManager.getClass().getName()); return new Object(); } public static void main(String[] args) { SpringApplication.run(ProfiledemoApplication.class, args); } }

 

 

在Spring容器中,咱們手工註解@Bean 將被優先加載,框架不會從新實例化其餘的 PlatformTransactionManager 實現類。

而後在Service中,被 @Transactional 註解的方法,將支持事務。若是註解在類上,則整個類的全部方法都默認支持事務。

對於同一個工程中存在多個事務管理器要怎麼處理,請看下面的實例,具體說明請看代碼中的註釋。

@EnableTransactionManagement // 開啓註解事務管理,等同於xml配置文件中的 <tx:annotation-driven /> @SpringBootApplication public class ProfiledemoApplication implements TransactionManagementConfigurer { @Resource(name="txManager2") private PlatformTransactionManager txManager2; // 建立事務管理器1 @Bean(name = "txManager1") public PlatformTransactionManager txManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } // 建立事務管理器2 @Bean(name = "txManager2") public PlatformTransactionManager txManager2(EntityManagerFactory factory) { return new JpaTransactionManager(factory); } // 實現接口 TransactionManagementConfigurer 方法,其返回值表明在擁有多個事務管理器的狀況下默認使用的事務管理器 @Override public PlatformTransactionManager annotationDrivenTransactionManager() { return txManager2; } public static void main(String[] args) { SpringApplication.run(ProfiledemoApplication.class, args); } }
@Component public class DevSendMessage implements SendMessage { // 使用value具體指定使用哪一個事務管理器 @Transactional(value="txManager1") @Override public void send() { System.out.println(">>>>>>>>Dev Send()<<<<<<<<"); send2(); } // 在存在多個事務管理器的狀況下,若是使用value具體指定 // 則默認使用方法 annotationDrivenTransactionManager() 返回的事務管理器 @Transactional public void send2() { System.out.println(">>>>>>>>Dev Send2()<<<<<<<<"); } }

 

注: 
若是Spring容器中存在多個 PlatformTransactionManager 實例,而且沒有實現接口 TransactionManagementConfigurer 指定默認值,在咱們在方法上使用註解 @Transactional 的時候,就必需要用value指定,若是不指定,則會拋出異常。

對於系統須要提供默認事務管理的狀況下,實現接口 TransactionManagementConfigurer 指定。

對有的系統,爲了不沒必要要的問題,在業務中必需要明確指定 @Transactional 的 value 值的狀況下。不建議實現接口 TransactionManagementConfigurer,這樣控制檯會明確拋出異常,開發人員就不會忘記主動指定。

事務詳解

上面的例子中咱們使用了默認的事務配置,能夠知足一些基本的事務需求,可是當咱們項目較大較複雜時(好比,有多個數據源等),這時候須要在聲明事務時,指定不一樣的事務管理器。對於不一樣數據源的事務管理配置能夠見 《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)
相關文章
相關標籤/搜索