一、事務(wù)的四個特性
l?原子性:一個事務(wù)中所有對數(shù)據(jù)庫的操作是一個不可分割的操作序列,要么全做,要么全部做。
l?一致性:數(shù)據(jù)不會因?yàn)槭聞?wù)的執(zhí)行而遭到破壞。
l?隔離性:一個事務(wù)的執(zhí)行,不受其他事務(wù)(進(jìn)程)的干擾。既并發(fā)執(zhí)行的個事務(wù)之間互不干擾。
l?持久性:一個事務(wù)一旦提交,它對數(shù)據(jù)庫的改變將是永久的。
二、事務(wù)的實(shí)現(xiàn)方式
????? 實(shí)現(xiàn)方式共有兩種:編碼方式;聲明式事務(wù)管理方式。
?基于AOP技術(shù)實(shí)現(xiàn)的聲明式事務(wù)管理,實(shí)質(zhì)就是:在方法執(zhí)行前后進(jìn)行攔截,然后在目標(biāo)方法開始之前創(chuàng)建并加入事務(wù),執(zhí)行完目標(biāo)方法后根據(jù)執(zhí)行情況提交或回滾事務(wù)。
聲明式事務(wù)管理又有兩種方式:一種是基于XML配置文件的方式;另一種是在業(yè)務(wù)方法上添加@Transactional注解,將事務(wù)規(guī)則應(yīng)用到業(yè)務(wù)邏輯中(一般定義在service層)。
三、創(chuàng)建事務(wù)的時機(jī)
????? 是否需要創(chuàng)建事務(wù),是由事務(wù)傳播行為控制的。讀數(shù)據(jù)不需要或只為其指定只讀事務(wù),而數(shù)據(jù)的插入、修改、刪除就需要進(jìn)行事務(wù)管理了,這是由事務(wù)的隔離級別控制的。
????? 事務(wù)具有7個傳播級別和4個隔離級別,傳播級別定義的是事務(wù)創(chuàng)建的時機(jī),隔離級別定義的是對并發(fā)事務(wù)數(shù)據(jù)讀取的控制。
四、事務(wù)的傳播級別
????? 以下是事務(wù)的7種傳播級別:
l?PROPAGATION_REQUIRED
默認(rèn)的Spring事務(wù)傳播級別,使用該級別的特點(diǎn)是:如果上下文中已經(jīng)存在事務(wù),那么就加入到事務(wù)中執(zhí)行;如果當(dāng)前上下文中不存在事務(wù),則新建事務(wù)執(zhí)行。所以這個級別通常能滿足處理大多數(shù)的業(yè)務(wù)場景。
l?PROPAGATION_SUPPORTS
從字面意思就知道,supports,支持,該傳播級別的特點(diǎn)是:如果上下文存在事務(wù),則支持事務(wù)加入事務(wù),如果沒有事務(wù),則使用非事務(wù)的方式執(zhí)行。所以說,并非所有的包含在TransactionTemplate.execute方法中的代碼都會有事務(wù)支持。這個通常是用來處理那些并非原子性的非核心業(yè)務(wù)邏輯操作。應(yīng)用場景較少。
l?PROPAGATION_MANDATORY
該級別的事務(wù)要求上下文中必須要存在事務(wù),否則就會拋出異常!配置該方式的傳播級別是有效的控制上下文調(diào)用代碼遺漏添加事務(wù)控制的保證手段。比如一段代碼不能單獨(dú)被調(diào)用執(zhí)行,但是一旦被調(diào)用,就必須有事務(wù)包含的情況,就可以使用這個傳播級別。
l?PROPAGATION_REQUIRES_NEW
從字面即可知道,new,每次都要一個新事務(wù),該傳播級別的特點(diǎn)是:每次都會新建一個事務(wù),并且同時將上下文中的事務(wù)掛起,執(zhí)行當(dāng)前新建事務(wù)完成以后,上下文事務(wù)恢復(fù)再執(zhí)行。
這是一個很有用的傳播級別,舉一個應(yīng)用場景:現(xiàn)在有一個發(fā)送100個紅包的操作,在發(fā)送之前,要做一些系統(tǒng)的初始化、驗(yàn)證、數(shù)據(jù)記錄操作,然后發(fā)送100封紅包,然后再記錄發(fā)送日志,發(fā)送日志要求100%的準(zhǔn)確,如果日志不準(zhǔn)確,那么整個父事務(wù)邏輯需要回滾。
怎么處理整個業(yè)務(wù)需求呢?就是通過這個PROPAGATION_REQUIRES_NEW級別的事務(wù)傳播控制就可以完成。發(fā)送紅包的子事務(wù)不會直接影響到父事務(wù)的提交和回滾。
l?PROPAGATION_NOT_SUPPORTED
這個也可以從字面得知,not supported,不支持,當(dāng)前級別的特點(diǎn)是:若上下文中存在事務(wù),則掛起事務(wù),執(zhí)行當(dāng)前邏輯,結(jié)束后恢復(fù)上下文的事務(wù)。
這個級別有什么好處?可以幫助你將事務(wù)盡可能的縮小。我們知道一個事務(wù)越大,它存在的風(fēng)險也就越多。所以在處理事務(wù)的過程中,要保證盡可能的縮小范圍。比如一段代碼,是每次邏輯操作都必須調(diào)用的,比如循環(huán)1000次的某個非核心業(yè)務(wù)邏輯操作。這樣的代碼如果包在事務(wù)中,勢必造成事務(wù)太大,導(dǎo)致出現(xiàn)一些難以考慮周全的異常情況,所以事務(wù)的這個傳播級別就派上用場了。用當(dāng)前級別的事務(wù)模板包含起來就可以了。
l?PROPAGATION_NEVER
該事務(wù)更嚴(yán)格,上面一個事務(wù)傳播級別只是不支持而已,有事務(wù)就掛起,而PROPAGATION_NEVER傳播級別要求上下文中不能存在事務(wù),一旦有事務(wù),就拋出runtime異常,強(qiáng)制停止執(zhí)行!這個級別上輩子跟事務(wù)有仇。
l?PROPAGATION_NESTED
從字面也可知道,nested,嵌套級別事務(wù)。該傳播級別的特征是:如果上下文中存在事務(wù),則嵌套事務(wù)執(zhí)行,如果不存在事務(wù),則新建事務(wù)。
那么什么是嵌套事務(wù)呢?很多人都不理解,我看過一些博客,都是有些理解偏差。
嵌套是子事務(wù)嵌套在父事務(wù)中執(zhí)行,子事務(wù)是父事務(wù)的一部分,在進(jìn)入子事務(wù)之前,父事務(wù)建立一個回滾點(diǎn),叫save point,然后執(zhí)行子事務(wù),這個子事務(wù)的執(zhí)行也算是父事務(wù)的一部分,然后子事務(wù)執(zhí)行結(jié)束,父事務(wù)繼續(xù)執(zhí)行。重點(diǎn)就在于那個save point??磶讉€問題就明了了:
(1)?? 如果子事務(wù)回滾,會發(fā)生什么?
父事務(wù)會回滾到進(jìn)入子事務(wù)前建立的save point,然后嘗試其他的事務(wù)或者其他的業(yè)務(wù)邏輯,父事務(wù)之前的操作不會受到影響,更不會自動回滾。
(2)?? 如果父事務(wù)回滾,會發(fā)生什么?
父事務(wù)回滾,子事務(wù)也會跟著回滾!為什么呢,因?yàn)楦甘聞?wù)結(jié)束之前,子事務(wù)是不會提交的,我們說子事務(wù)是父事務(wù)的一部分,正是這個道理。
(3)?? 事務(wù)的提交,是什么情況?
是父事務(wù)先提交,然后子事務(wù)提交,還是子事務(wù)先提交,父事務(wù)再提交?答案是第二種情況,還是那句話,子事務(wù)是父事務(wù)的一部分,由父事務(wù)統(tǒng)一提交。
現(xiàn)在你再體會一下這個”嵌套“,是不是有那么點(diǎn)意思了?
以上是事務(wù)的7個傳播級別,在日常應(yīng)用中,通常可以滿足各種業(yè)務(wù)需求,但是除了傳播級別,在讀取數(shù)據(jù)庫的過程中,如果兩個事務(wù)并發(fā)執(zhí)行,那么彼此之間的數(shù)據(jù)是如何影響的呢?這就需要了解一下事務(wù)的另一個特性:數(shù)據(jù)隔離級別。
五、事務(wù)的隔離級別
????? 數(shù)據(jù)隔離級別分為不同的4種:
l?SERIALIZABLE
最嚴(yán)格的級別,事務(wù)串行執(zhí)行,資源消耗最大。
l?REPEATABLE_READ
保證了一個事務(wù)不會修改已經(jīng)由另一個事務(wù)讀取但未提交(回滾)的數(shù)據(jù)。避免了“臟讀取”和“不可重復(fù)讀取”的情況,但是帶來了更多的性能損失。
l?READ_COMMITTED
大多數(shù)主流數(shù)據(jù)庫的默認(rèn)事務(wù)等級,保證了一個事務(wù)不會讀到另一個并行事務(wù)已修改但未提交的數(shù)據(jù),避免了“臟讀取”。該級別適用于大多數(shù)系統(tǒng)。
l?READ_UNCOMMITTED
保證了讀取過程中不會讀取到非法數(shù)據(jù)。
????? 上面的解釋其實(shí)每個定義都有一些拗口,其中涉及到幾個術(shù)語:臟讀、不可重復(fù)讀、幻讀。這里解釋一下:
l?臟讀(Dirty Reads)
所謂的臟讀,其實(shí)就是讀到了別的事務(wù)回滾前的臟數(shù)據(jù)。比如事務(wù)B執(zhí)行過程中修改了數(shù)據(jù)X,在未提交前,事務(wù)A讀取了X,而事務(wù)B卻回滾了,這樣事務(wù)A就形成了臟讀。
l?不可重復(fù)讀(Non-RepeatableReads)
不可重復(fù)讀字面含義已經(jīng)很明了了,比如事務(wù)A首先讀取了一條數(shù)據(jù),然后執(zhí)行邏輯的時候,事務(wù)B將這條數(shù)據(jù)改變了,然后事務(wù)A再次讀取的時候,發(fā)現(xiàn)數(shù)據(jù)不匹配了,就是所謂的不可重復(fù)讀了。
l?幻讀
小的時候數(shù)手指,第一次數(shù)十10個,第二次數(shù)是11個,怎么回事?產(chǎn)生幻覺了?
幻讀也是這樣子,事務(wù)A首先根據(jù)條件索引得到10條數(shù)據(jù),然后事務(wù)B改變了數(shù)據(jù)庫一條數(shù)據(jù),導(dǎo)致也符合事務(wù)A當(dāng)時的搜索條件,這樣事務(wù)A再次搜索發(fā)現(xiàn)有11條數(shù)據(jù)了,就產(chǎn)生了幻讀。
事務(wù)隔離級別對照關(guān)系表:

? 所以最安全的,是Serializable,但是伴隨而來也是高昂的性能開銷。
另外,事務(wù)常用的兩個屬性:①?readonly,設(shè)置事務(wù)為只讀以提升性能;②?timeout,設(shè)置事務(wù)的超時時間,一般用于防止大事務(wù)的發(fā)生。
六、Spring管理聲明式事務(wù)的配置
1. 基于XML配置文件的AOP和TX配置方式
????? applicationContext.xml配置文件:
七、簡述Hibernate的SessionFactory和Session
l? SessionFactory對象
Hibernate中SessionFactory對象的創(chuàng)建代價很高,它是線程安全的對象,被設(shè)計(jì)成可以為所有的應(yīng)用程序線程所共享。通常,SessionFactory會在應(yīng)用程序啟動時創(chuàng)建,一旦創(chuàng)建了SessionFactory將不會輕易關(guān)閉,只有當(dāng)應(yīng)用關(guān)閉時,SessionFactory才會關(guān)閉。
l? Session對象
Hibernate中Session對象是輕量級的,它是線程不安全的。對于單個業(yè)務(wù)進(jìn)程、單個工作單元而言,Session只被使用一次。創(chuàng)建Session時,并不會立即打開與數(shù)據(jù)庫之間的連接,Session只在需要進(jìn)行數(shù)據(jù)庫操作時,才會獲取JDBC連接。因此,打開和關(guān)閉Session,并不會對性能造成很大的影響。甚至即使無法確定一個請求是否需要數(shù)據(jù)訪問,也可以打開Session對象,因?yàn)槿绻贿M(jìn)行數(shù)據(jù)庫訪問,Session不會獲取JDBC連接。
使用Spring管理Hibernate的事務(wù),在每個DAO操作中使用SessionFactory.getCurrentSession()方法,該方法可以得到當(dāng)前事務(wù)綁定的Session。同時當(dāng)前的Session和關(guān)聯(lián)的Hibernate事務(wù)被綁定到當(dāng)前線程上,雖然Session不是線程安全的,但是通過這樣的方式,每一個Session都處于單線程中,避免Session線程安全問題。
l? 不通過Spring管理事務(wù),開啟事務(wù)的主動性
在SessionFactory.openSession()中,Hibernate會初始化數(shù)據(jù)庫連接,與此同時,將其 AutoCommit設(shè)為關(guān)閉狀態(tài),這就是說,從SessionFactory獲得Session,其自動提交屬性就已經(jīng)被關(guān)閉了,事務(wù)需要主動、顯示的調(diào)用才能生效,下面的代碼不會對事務(wù)性數(shù)據(jù)庫產(chǎn)生任何效果:
?session?= sessionFactory.openSession();
?session.save(user);
?session.close();
? 如果要使得代碼真正作用到數(shù)據(jù)庫,必須顯示的調(diào)用Transaction指令:
?session?= sessionFactory.openSession();
Transaction?tx?=?session.beginTransaction();
?session.save(user);
?tx.commit();
?session.close();