springboot【14】事務管理
什麼是事務?
我們在開發企業應用時,對於業務人員的一個操作實際是對資料讀寫的多步操作的結合。由於資料操作在順序執行的過程中,任何一步操作都有可能發生異常,異常會導致後續操作無法完成,此時由於業務邏輯並未正確的完成,之前成功操作資料的並不可靠,需要在這種情況下進行回退。
事務的作用就是為了保證使用者的每一個操作都是可靠的,事務中的每一步操作都必須成功執行,只要有發生異常就回退到事務開始未進行操作的狀態。
事務管理是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進行資料讀寫的單元測試用例,如下:
package com.lyd; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.transaction.annotation.Transactional; import com.lyd.domain.User; import com.lyd.domain.UserRepository; @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(Application.class) public class ApplicationTest { @Autowired private UserRepository userService; @Test //@Transactional public void test(){ // 建立10條記錄 userService.save(new User("AAA", 10)); userService.save(new User("BBB", 20)); userService.save(new User("CCC", 30)); userService.save(new User("DDD", 40)); userService.save(new User("EEE", 50)); userService.save(new User("FFF", 60)); userService.save(new User("GGG", 70)); userService.save(new User("HHH", 80)); userService.save(new User("III", 90)); userService.save(new User("JJJ", 100)); // 測試findAll, 查詢所有記錄 Assert.assertEquals(10, userService.findAll().size()); } }
在這個單元測試用例中,使用UserRepository物件連續建立了10個User實體到資料庫中,下面人為地來製造一些異常,看看會發生什麼情況。
通過定義User的name屬性長度為5,這樣通過建立時User實體的name屬性超長就可以觸發異常產生。
package com.lyd.domain; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; @Entity public class User { @Id @GeneratedValue private Long id; @Column(nullable = false, length = 5) private String name; @Column(nullable = false) private Integer age; // 建構函式 // get/set方法... }
修改測試用例中建立記錄的語句,將一條記錄的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欄位超長:
2017-08-08 10:30:35.948 WARN 2660 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 1406, SQLState: 22001
2017-08-08 10:30:35.948 ERROR 2660 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : Data truncation: Data too long for column 'name' at row 1
2017-08-08 10:30:35.951 WARN 2660 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Warning Code: 1406, SQLState: HY000
2017-08-08 10:30:35.951 WARN 2660 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : Data too long for column 'name' at row 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)
2017-08-08 10:35:32.210 WARN 5672 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 1406, SQLState: 22001
2017-08-08 10:35:32.210 ERROR 5672 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : Data truncation: Data too long for column 'name' at row 1
2017-08-08 10:35:32.213 WARN 5672 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Warning Code: 1406, SQLState: HY000
2017-08-08 10:35:32.213 WARN 5672 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : Data too long for column 'name' at row 1
2017-08-08 10:35:32.221 INFO 5672 --- [ main] o.s.t.c.transaction.TransactionContext : Rolled back transaction for test context [[email protected] testClass = ApplicationTests, testInstance = [email protected], testMethod = [email protected], testException = org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; nested exception is org.hibernate.exception.DataException: could not execute statement, mergedContextConfiguration = [[email protected] 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);
}
事務詳解
上面的例子中我們使用了預設的事務配置,可以滿足一些基本的事務需求,但是當我們專案較大較複雜時(比如,有多個數據源等),這時候需要在宣告事務時,指定不同的事務管理器。對於不同資料來源的事務管理配置可以參考中的設定。在宣告事務時,只需要通過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)
下面是完整專案結構:
相關推薦
springboot【14】事務管理
什麼是事務? 我們在開發企業應用時,對於業務人員的一個操作實際是對資料讀寫的多步操作的結合。由於資料操作在順序執行的過程中,任何一步操作都有可能發生異常,異常會導致後續操作無法完成,此時由於業務邏輯並未正確的完成,之前成功操作資料的並不可靠,需要在這種情況
springboot【23】監控管理之Actuator監控端點實踐
在傳統Spring應用中使用spring-boot-actuator模組提供監控端點 在Spring Boot應用中,我們只需要簡單的引入spring-boot-starter-actuator依賴就能為應用新增各種有用的監控端點。其中,/health端點能夠全面檢
springboot【24】監控管理之Actuator的/info端點輸出Git版本資訊
springboot的Actuator模組中有個特殊端點/info除了描述應用資訊之外,也還可以用來描述Git版本資訊,並且整合方法非常簡單,下面我們就來看看如何使用/info端點暴露當前應用的Git版本資訊。 POM配置 首先,我們可以挑選任意一個Sp
【spring】事務管理
一、事務 1.1事物的特性 原子性、永續性、一致性、隔離性 1.2不考慮隔離性存在的安全問題 讀問題:髒讀、不可重複讀、幻讀、
【Ubuntu】任務管理器loadruner
grep rexec ref con included init.d 返回 find ubun linux1 準備工作 可以通過兩種方法驗證服務器上是否配置了rstatd守護程序: ①使用rup命令,它用於報告計算機的各種統計信息,其中就包括rstatd的
【Linux】系統管理
kcon status gcc 不知道 切換 all usr make entos 軟件包管理 一 軟件包分類 源碼包: .tar.gz .tar.bz2 二進制包: .rpm 二 二進制包安裝 (一) rpm命令手動管理二進制包 (掛載光盤)
saltstack主機管理項目【day39】:主機管理項目開發
ask 項目 sts llb valid -c proc ltm bin 項目目標 salt state.apply -h "ubuntu,centos" -g "ubuntu,centos" -f "ubuntu,centos"
【14】redux 之 redux-actions
scrip reset case 原來 from reat sin con spa redux-actions有兩大法寶createAction和handleActions. createAction http://www.jianshu.com/p/6ba5cd79507
【科普】銷售管理軟件的功能是什麽?
管理系統對於企業來說,贏得了客戶信賴,就預示著企業的成功,而企業面對眾多客戶,其中銷售管理軟件起到了很大的作用,能夠幫助企業實現快速管理跟進客戶、合同快速達成等,大大的提高了企業的辦公效率和效益,同時,對於企業來說,無規矩不成方圓,規範管理是企業的基礎,是向客戶提供更好服務的保障,而規範企業管理,這也是銷售管
【六】MongoDB管理之副本集
bottom reference mil 沒有 options 過程 新版 會有 滿足 一、復制介紹 所謂的復制就是在多個主機之間同步數據的過程。 1、數據冗余及可用性 復制技術提供數據冗余及可用性,在不同的數據庫服務器上使用多個數據副本,復制技術防止單個數據庫服務器出現數
【九】MongoDB管理之安全性
方法 開啟 oot backup 由於 alt 集群管理 失敗 exec 要保證一個安全的MongoDB運行環境,DBA需要實施一些控制保證用戶或應用程序僅僅訪問它們需要的數據。這些措施包括但不限於: 認證機制 基於角色的訪問控制 加密 審計 一、認證機制 認證是驗證客
【BZOJ3648】寢室管理 樹分治
數組 沒有 != 決定 算法 microsoft put ron 1+n 【BZOJ3648】寢室管理 Description T64有一個好朋友,叫T128。T128是寄宿生,並且最近被老師叫過去當宿管了。宿管可不是一件很好做的工作,碰巧T128有一個工作上的問題
【14】where字句
where子句 篩選條件 exe [] where 如果 from 左值 per 1.簡介 -> where緊跟在from後執行,用來初選-> 語法where 條件-> 註意-> where子句對數據源直接進行篩選-> 篩選可以是表達式、判斷、
【綜合】事務的處理及隔離級別
產生 數據表 查看 普通 模式 再次 blank 並發執行 gpo 原文地址:http://blog.csdn.net/qiaoge134/article/details/20031949 事務的隔離級別: 先說說 (通俗說) 1. 臟讀:是一個事務讀取了 其他事務沒有
MySQL基礎入門學習【14】存儲引擎
nsis ont 過程 關系 ali 余額 bili 事務 atom 查看數據表的創建命令: 【存儲引擎】: MySQL可以將數據以不同的奇數存儲在文件(內存)中,這種技術就成為存儲引擎 每一種存儲引擎都使用了不同的存儲機制、索引技巧、鎖定水平,最
【MongoDB】MongoDB管理:使用killOp幹掉Long Running Operation
官方 pair 可能 建立 找到 ecs 官方文檔 ntop mongodb http://www.mongoing.com/archives/2563 MongoDB提供了killOp請求,用於幹掉運行時間很長的請求,killOp通常需要與currentOp組合起來使用;
8、【C++】記憶體管理
C++記憶體管理 一、記憶體分配方式 在C++中記憶體被分為5個區,分別是:棧區、堆區、自由儲存區、全域性/靜態儲存區和常量儲存區。 1、棧區 在執行函式時,函式內區域性變數的儲存單元都可一在棧上建立,函式執行結束時,這些儲存單元自動被釋放。棧記憶體分配運算內建與
【原始碼】記憶體管理--得記憶體者得天下
程序和記憶體管理堪稱核心的任督二脈,是最重要的兩部分,這兩部弄清楚了,主體架構也就確立,其它都是支脈。而這兩者中,又數記憶體管理最難,所以,得記憶體者得天下。 (一) 1.buddy(夥伴)機制。 以頁為單位的大記憶體。 2.slab機制。 管
LeetCode:最長公共字首【14】
LeetCode:最長公共字首【14】 題目描述 編寫一個函式來查詢字串陣列中的最長公共字首。 如果不存在公共字首,返回空字串 ""。 示例 1: 輸入: ["flower","flow","flight"] 輸出: "fl" 示例 2: 輸入: ["dog","
LeetCode:最長公共前綴【14】
true 編寫 pub class solution leetcode int npr car LeetCode:最長公共前綴【14】 題目描述 編寫一個函數來查找字符串數組中的最長公共前綴。 如果不存在公共前綴,返回空字符串 ""。 示例 1: 輸入: ["flower"