事務中包含的操做被看做一個總體的業務單元,這個業務單元中的操做要麼所有成功,要麼所有失敗,不會出現部分失敗和部分紅功的場景
併發
事務在完成時,必須使全部的數據都保持一直狀態,在數據庫中全部的修改都基於事務,保證了數據的完整性app
多個應用程序線程同時訪問統一數據,這樣數據庫一樣的數據就會在各個不一樣的事務中被訪問,這樣會產生丟失更新,爲了壓制丟失更新的產生,數據庫定義了隔離級別的概念,經過它的選擇,能夠在不一樣程度上壓制丟失更新的產生。由於互聯網的應用經常面對高併發的場景,因此隔離性是須要掌握的重點內容高併發
事務結束後,全部的數據都會固化到一個地方,如保存磁盤當中,即時斷電重啓後也能夠提供應用程序訪問性能
爲了壓制丟失更新,數據庫提出了隔離級別,在不一樣程度上壓制更新spa
也許會有一個疑問,都所有消除丟失更新不就行了嗎,爲何只是在不一樣的程度.上壓制丟失更新呢?線程
其實這個問題是從兩個角度去看的,一個是數據的一致性,另外一個是性能。數據庫現有的技術徹底能夠避免丟失更新,可是這樣作的代價,就是付出鎖的代價,在互聯網中,系統不僅僅要考慮數據的-致性,還要考慮系統的性能。試想,在互聯網中使用過多的鎖,--旦出現商品搶購這樣的場景,必然會致使大量的線程被掛起和恢復,由於使用了鎖以後,一個時刻只能有一個線程訪問數據,這樣整個系統就會十分緩慢,當系統被數千甚至數萬用戶同時訪問時,過多的鎖就會引起宕機,大部分用戶線程被掛起,等待持有鎖事務的完成,這樣用戶體驗就會十分糟糕。由於用戶等待的時間會十分漫長,通常而言,互聯網系統響應超過5秒,就會讓用戶以爲很不友好,進而引起用戶忠誠度降低的問題。因此選擇隔離級別的時候,既須要考慮數據的一致性避免髒數據,又要考慮系統性能的問題。所以數據庫的規範就提出了4種隔離級別來在不一樣的程度上壓制丟失更新。設計
最低的隔離級別,含義是容許一個事務讀取另一個事務沒有提交的數據。未提交讀是一種危險的隔離級別,實際開發中應用不廣3d
指一個事務只能讀取一個事務已經提交的數據,不能讀取未提交的數據code
可重複讀的目標是克服讀寫提交中出現的不可重複讀的現象,由於在讀寫提交的時候,可能出現一些值的變化,影響當前事務的執行
數據庫最高的隔離級別,它會要求全部的SQL都會按照順序執行,這樣就能夠克服上訴隔離級別出現的各類問題,因此它能徹底保證數據的一致性
追求更高的隔離級別,它能更好地保證數據的一致性,可是也要付出鎖的代價。有了鎖,就意味着性能的丟失,並且隔離級別越高,性能越是直線降低。
因此在選擇隔離級別時,要考慮的不僅僅是數據一致性問題,還要考慮系統的性能問題
通常而言,選擇隔離級別會以讀寫提交爲主,它能防止髒讀,而不能避免不可重複讀和幻讀,爲了克服數據不一致性和性能問題,程序開發者還設計了樂觀鎖,甚至再也不使用數據庫而使用其餘手段
對於隔離級別,不一樣的數據庫支持也是不同的
在Spring中,當一個方法調用另一個方法,可讓事務採起不一樣的策略工做,如新建事務或掛起當前事務
在一個批量任務執行的過程當中,調用多個交易時,若是有一些交易發生異常,只是回滾出現異常的交易,而不是裏整個批量任務,這樣就可以是的那些沒有問題的交易能夠吮吸完成,而有問題的交易則不作任何事情
對於聲明式事務,使用@Transaction進行標註,可標註在類活着方法上,當它標註在類上時,表明這個類全部公共(public)非靜態的方法都將啓用事務功能
默認配置下 Spring 只會回滾運行時、未檢查異常(繼承自 RuntimeException 的異常)或者 Error。
有了@Transcation,Spring就會知道從哪啓動事務,約定流程:
當上下文開始調用被@Transcation標註的類或者方法時,Spring就會產生AOP的功能。請注意事務的底層須要啓動AOP功能,這就是Spring事務的底層實現
若有一個保存用戶的方法,加入 @Transactional 註解,使用默認配置,拋出異常以後,事務會自動回滾,數據不會插入到數據庫。
@RestController public class HouseController { @Autowired private HouseRepository houseRepository; @GetMapping("/test1") public String test1(){ houseRepository.save(new House("house1", "100平方米")); houseRepository.save(new House("house2", "100平方米")); houseRepository.save(new House("house3", "100平方米")); houseRepository.save(new House("house444444444", "100平方米")); houseRepository.save(new House("house5", "100平方米")); return "success"; } @GetMapping("/test2") @Transactional public String test2(){ houseRepository.save(new House("house6", "100平方米")); houseRepository.save(new House("house7", "100平方米")); houseRepository.save(new House("house8", "100平方米")); houseRepository.save(new House("house999999999", "100平方米")); houseRepository.save(new House("house10", "100平方米")); return "success"; } }
test1方法沒有加入事務,test2方法加入了事務註解。
test1:當輸入http://localhost:8888/test1
數據庫中插入了三條數據
由於第四條數據太長,因此插入失敗,致使第五條正常數據插入失敗,這並非咱們想要的,要麼全成功,要麼全失敗
test2:輸入http://localhost:8888/test2
數據庫中數據不變依然是三條數據,插入失敗,因此所有回滾
本文借鑑:《深刻淺出Spring Boot 2.x》書中有更詳細的例子