事物的特性(ACID)
原子性: 事務是最小的執(zhí)行單位蚀苛,不允許分割令杈。事務的原子性確保動作要么全部完成,要么完全不起作用养葵;
一致性: 執(zhí)行事務前后入客,數(shù)據(jù)保持一致;
隔離性: 并發(fā)訪問數(shù)據(jù)庫時嚎莉,一個用戶的事物不被其他事物所干擾米酬,各并發(fā)事務之間數(shù)據(jù)庫是獨立的;
持久性: 一個事務被提交之后趋箩。它對數(shù)據(jù)庫中數(shù)據(jù)的改變是持久的赃额,即使數(shù)據(jù)庫發(fā)生故障也不應該對其有任何影響。
我們在使用JDBC或者Mybatis進行數(shù)據(jù)持久化操作時,我們的xml配置通常如下:
<!-- 事務管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 數(shù)據(jù)源 -->
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 開啟事務行為 -->
<tx:annotation-driven transaction-manager="transactionManager" />
并發(fā)事務帶來的問題
在典型的應用程序中叫确,多個事務并發(fā)運行跳芳,經(jīng)常會操作相同的數(shù)據(jù)來完成各自的任務(多個用戶對統(tǒng)一數(shù)據(jù)進行操作)。并發(fā)雖然是必須的竹勉,但可能會導致一下的問題飞盆。
臟讀(Dirty read): 當一個事務正在訪問數(shù)據(jù)并且對數(shù)據(jù)進行了修改,而這種修改還沒有提交到數(shù)據(jù)庫中次乓,這時另外一個事務也訪問了這個數(shù)據(jù)吓歇,然后使用了這個數(shù)據(jù)。因為這個數(shù)據(jù)是還沒有提交的數(shù)據(jù)票腰,那么另外一個事務讀到的這個數(shù)據(jù)是“臟數(shù)據(jù)”城看,依據(jù)“臟數(shù)據(jù)”所做的操作可能是不正確的。
丟失修改(Lost to modify): 指在一個事務讀取一個數(shù)據(jù)時杏慰,另外一個事務也訪問了該數(shù)據(jù)测柠,那么在第一個事務中修改了這個數(shù)據(jù)后,第二個事務也修改了這個數(shù)據(jù)逃默。這樣第一個事務內(nèi)的修改結(jié)果就被丟失鹃愤,因此稱為丟失修改。
例如:事務1讀取某表中的數(shù)據(jù)A=20完域,事務2也讀取A=20软吐,事務1修改A=A-1,事務2也修改A=A-1吟税,最終結(jié)果A=19凹耙,事務1的修改被丟失。不可重復讀(Unrepeatableread): 指在一個事務內(nèi)多次讀同一數(shù)據(jù)肠仪。在這個事務還沒有結(jié)束時肖抱,另一個事務也訪問該數(shù)據(jù)。那么异旧,在第一個事務中的兩次讀數(shù)據(jù)之間意述,由于第二個事務的修改導致第一個事務兩次讀取的數(shù)據(jù)可能不太一樣。這就發(fā)生了在一個事務內(nèi)兩次讀到的數(shù)據(jù)是不一樣的情況,因此稱為不可重復讀荤崇。
幻讀(Phantom read): 幻讀與不可重復讀類似拌屏。它發(fā)生在一個事務(T1)讀取了幾行數(shù)據(jù),接著另一個并發(fā)事務(T2)插入了一些數(shù)據(jù)時术荤。在隨后的查詢中倚喂,第一個事務(T1)就會發(fā)現(xiàn)多了一些原本不存在的記錄,就好像發(fā)生了幻覺一樣瓣戚,所以稱為幻讀端圈。
不可重復度和幻讀區(qū)別:
不可重復讀的重點是修改,幻讀的重點在于新增或者刪除子库。
spring事務的傳播性舱权、隔離性
@Transactional
1.這里說明一下,有的把這個注解放在類名稱上面了刚照,這樣你配置的這個@Transactional 對這個類中的所有public方法都起作用.
2.@Transactional 方法方法名上刑巧,只對這個方法有作用,同樣必須是public的方法
我們在使用Spring聲明式事務時无畔,有一個非常重要的概念就是事務的屬性啊楚。事務屬性通常由事務的傳播行為、事務的隔離級別浑彰、事務的超時值和事務的只讀標識組成恭理。我們在進行事務劃分時,需要進行事務定義郭变,也就是配置事務的屬性颜价。
Spring在TransactionDefinition接口中定義這些屬性,以供PlatfromTransactionManager使用, PlatfromTransactionManager是spring事務管理的核心接口。
public interface TransactionDefinition {
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
int ISOLATION_DEFAULT = -1; //默認的隔離級別
int ISOLATION_READ_UNCOMMITTED = 1;
int ISOLATION_READ_COMMITTED = 2;
int ISOLATION_REPEATABLE_READ = 4;
int ISOLATION_SERIALIZABLE = 8;
int TIMEOUT_DEFAULT = -1;
int getPropagationBehavior();
int getIsolationLevel();
int getTimeout();
boolean isReadOnly();
String getName();
}
getTimeout()方法诉濒,它返回事務必須在多少秒內(nèi)完成周伦。
isReadOnly(),事務是否只讀,事務管理器能夠根據(jù)這個返回值進行優(yōu)化未荒,確保事務是只讀的专挪。
getIsolationLevel()方法返回事務的隔離級別,事務管理器根據(jù)它來控制另外一個事務可以看到本事務內(nèi)的哪些數(shù)據(jù)片排。
隔離級別
在TransactionDefinition接口中定義了五個不同的事務隔離級別 :
a) ISOLATION_DEFAULT:(PlatfromTransactionManager的)默認的隔離級別寨腔。使用數(shù)據(jù)庫默認的事務隔離級別(MySql 默認為 REPEATABLE_READ;Oracle 默認為:READ_COMMITTED)率寡,另外四個與JDBC的隔離級別相對應 迫卢。
b)ISOLATION_READ_UNCOMMITTED:這是事務最低的隔離級別,它允許別外一個事務可以看到這個事務未提交的數(shù)據(jù)冶共。這種隔離級別會產(chǎn)生臟讀乾蛤,不可重復讀和幻像讀每界。 (未解決任何并發(fā)問題)
例如:
Mary的原工資為1000,財務人員將Mary的工資改為了8000,但未提交事務
Connection con1 = getConnection();
con1.setAutoCommit(false);
update employee set salary = 8000 where empId ="Mary";
與此同時幻捏,Mary正在讀取自己的工資 盆犁。
Connection con2 = getConnection();
select salary from employee where empId ="Mary";
con2.commit();
Mary發(fā)現(xiàn)自己的工資變?yōu)榱?000命咐,歡天喜地篡九!
而財務發(fā)現(xiàn)操作有誤,而回滾了事務,Mary的工資又變?yōu)榱?000 醋奠。
con1.rollback();
像這樣,Mary記取的工資數(shù)8000是一個臟數(shù)據(jù)榛臼。
c)ISOLATION_READ_COMMITTED: 保證一個事務修改的數(shù)據(jù)提交后才能被另外一個事務讀取。另外一個事務不能讀取該事務未提交的數(shù)據(jù)窜司。這種事務隔離級別可以避免臟讀出現(xiàn)沛善,但是可能會出現(xiàn)不可重復讀和幻像讀。
d)ISOLATION_REPEATABLE_READ : 這種事務隔離級別可以防止臟讀塞祈,不可重復讀金刁。但是可能出現(xiàn)幻像讀。它除了保證一個事務不能讀取另一個事務未提交的數(shù)據(jù)外议薪,還保證了避免下面的情況產(chǎn)生(不可重復讀)尤蛮。
例如:
在事務1中,Mary 讀取了自己的工資為1000,操作并沒有完成 :
Connection con1 = getConnection();
con1.setAutoCommit(false);
select salary from employee empId ="Mary";
在事務2中斯议,這時財務人員修改了Mary的工資為2000,并提交了事務.
Connection con2 = getConnection();
update employee set salary = 2000;
con2.commit();
在事務1中产捞,Mary 再次讀取自己的工資時,工資變?yōu)榱?000
//con1
select salary from employee empId ="Mary";
在一個事務中前后兩次讀取的結(jié)果并不致哼御,導致了不可重復讀坯临。
使用ISOLATION_REPEATABLE_READ可以避免這種情況發(fā)生。
e)ISOLATION_SERIALIZABLE 這是花費最高代價但是最可靠的事務隔離級別恋昼。事務被處理為順序執(zhí)行看靠。除了防止臟讀,不可重復讀外液肌,還避免了幻像讀挟炬。
例如:
目前工資為1000的員工有10人。
事務1,讀取所有工資為1000的員工矩屁。共讀取10條記錄
con1 = getConnection();
Select * from employee where salary =1000;
這時另一個事務向employee表插入了一條員工記錄辟宗,工資也為1000
con2 = getConnection();
Insert into employee(empId,salary) values("Lili",1000);
con2.commit();
事務1再次讀取所有工資為1000的員工
//con1
select * from employee where salary =1000;
共讀取到了11條記錄,這就產(chǎn)生了幻像讀吝秕。
ISOLATION_SERIALIZABLE能避免這樣的情況發(fā)生泊脐。但是這樣也耗費了最大的資源。
事務的傳播性
在TransactionDefinition接口中定義了七個事務傳播行為烁峭。
假如我寫了兩個service類容客。名字分別為ServiceA和ServiceB秕铛。如下:
@Service("ServiceA")
public class ServiceA {
@Resource(name="ServiceB")
private ServiceB serviceB;
@Transactional(propagation=Propagation.REQUIRED)
public methodA(){
//doSomething
serviceB.methodB();
//doSomething
}
}
@Service("ServiceB")
public class ServiceB {
@Transactional(propagation=Propagation.REQUIRED)
public methodB(){
//doSomething
}
}
a)PROPAGATION_REQUIRED:如果存在一個事務,則支持當前事務缩挑。如果沒有事務則開啟一個新的事務但两。
如果單獨調(diào)用serviceB.methodB方法:
public static void main(String[] args) {
serviceB.methodB();
}
相當于:
public static void main(String[] args) {
Connection con=null;
try{
con = getConnection();
con.setAutoCommit(false);
//方法調(diào)用
methodB();
//提交事務
con.commit();
}Catch(RuntimeException ex){
//回滾事務
con.rollback();
}finally{
//釋放資源
closeCon();
}
}
Spring保證在methodB方法中所有的調(diào)用都獲得到一個相同的連接。在調(diào)用methodB時供置,沒有一個存在的事務谨湘,所以獲得一個新的連接,開啟了一個新的事務芥丧。
如果單獨調(diào)用MethodA時紧阔,在MethodA內(nèi)又會調(diào)用MethodB.
執(zhí)行效果相當于:
public static void main(String[] args) {
Connection con = null;
try{
con = getConnection();
con.setAutoCommit(false);
methodA();
con.commit();
}cathc(RuntimeException ex){
con.rollback();
}finally{
closeCon();
}
}
調(diào)用MethodA時,環(huán)境中沒有事務续担,所以開啟一個新的事務.
當在MethodA中調(diào)用MethodB時擅耽,環(huán)境中已經(jīng)有了一個事務,所以methodB就加入當前事務物遇。
b)PROPAGATION_SUPPORTS :如果存在一個事務乖仇,支持當前事務。如果沒有事務询兴,則非事務的執(zhí)行乃沙。但是對于事務同步的事務管理器,PROPAGATION_SUPPORTS與不使用事務有少許不同蕉朵。
//事務屬性 PROPAGATION_REQUIRED
methodA(){
serviceB.methodB();
}
//事務屬性 PROPAGATION_SUPPORTS
methodB(){
……
}
單純的調(diào)用methodB時崔涂,methodB方法是非事務的執(zhí)行的。
當調(diào)用methdA時,methodB則加入了methodA的事務中,事務地執(zhí)行始衅。
c)PROPAGATION_MANDATORY :如果已經(jīng)存在一個事務冷蚂,支持當前事務。如果沒有一個活動的事務汛闸,則拋出異常蝙茶。
//事務屬性 PROPAGATION_REQUIRED
methodA(){
serviceB.methodB();
}
//事務屬性 PROPAGATION_MANDATORY
methodB(){
……
}
當單獨調(diào)用methodB時,因為當前沒有一個活動的事務诸老,則會拋出異常
當調(diào)用methodA時隆夯,methodB則加入到methodA的事務中,事務地執(zhí)行别伏。
d)PROPAGATION_REQUIRES_NEW :總是開啟一個新的事務蹄衷。如果一個事務已經(jīng)存在,則將這個存在的事務掛起厘肮。
//事務屬性 PROPAGATION_REQUIRED
methodA(){
doSomeThingA();
serviceB.methodB();
doSomeThingB();
}
//事務屬性 PROPAGATION_REQUIRES_NEW
methodB(){
……
}
當單獨調(diào)用methodB時愧口,相當于把methodB聲明為REQUIRED。開啟一個新的事務类茂,事務地執(zhí)行耍属。
當調(diào)用methodA時,情況就大不一樣了托嚣,相當于下面的效果。
public static void main(String[] args) {
TransactionManager tm = null;
try{
//獲得一個JTA事務管理器
tm = getTransactionManager();
tm.begin();//開啟一個新的事務
Transaction ts1 = tm.getTransaction();
doSomeThing();
tm.suspend();//掛起當前事務
try{
tm.begin();//重新開啟第二個事務
Transaction ts2 = tm.getTransaction();
methodB();
ts2.commit();//提交第二個事務
}Catch(RunTimeException ex){
ts2.rollback();//回滾第二個事務
}finally{
//釋放資源
}
//methodB執(zhí)行完后厚骗,復恢第一個事務
tm.resume(ts1);
doSomeThingB();
ts1.commit();//提交第一個事務
}catch(RunTimeException ex){
ts1.rollback();//回滾第一個事務
}finally{
//釋放資源
}
}
在這里示启,我把ts1稱為外層事務,ts2稱為內(nèi)層事務领舰。從上面的代碼可以看出夫嗓,ts2與ts1是兩個獨立的事務,互不相干提揍。Ts2是否成功并不依賴于ts1啤月。如果methodA方法在調(diào)用methodB方法后的doSomeThingB方法失敗了,而methodB方法所做的結(jié)果依然被提交劳跃。而除了methodB之外的其它代碼導致的結(jié)果卻被回滾了。
使用PROPAGATION_REQUIRES_NEW,需要使用JtaTransactionManager作為事務管理器浙垫。
e)PROPAGATION_NOT_SUPPORTED: 總是非事務地執(zhí)行刨仑,并掛起任何存在的事務。
事務屬性 PROPAGATION_REQUIRED
methodA(){
doSomeThingA();
serviceB.methodB();
doSomeThingB();
}
事務屬性 PROPAGATION_NOT_SUPPORTED
methodB(){
……
}
當單獨調(diào)用methodB時夹姥,不啟用任何事務機制杉武,非事務地執(zhí)行。
當調(diào)用methodA時辙售,相當于下面的效果
public static void main(String[] args) {
TransactionManager tm = null;
try{
//獲得一個JTA事務管理器
tm = getTransactionManager();
tm.begin();//開啟一個新的事務
Transaction ts1 = tm.getTransaction();
doSomeThing();
tm.suspend();//掛起當前事務
methodB();
//methodB執(zhí)行完后轻抱,復恢第一個事務
tm.resume(ts1);
doSomeThingB();
ts1.commit();//提交第一個事務
}catch(RunTimeException ex){
ts1.rollback();//回滾第一個事務
}finally{
//釋放資源
}
}
使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作為事務管理器。
f)PROPAGATION_NEVER :總是非事務地執(zhí)行旦部,如果存在一個活動事務祈搜,則拋出異常:
事務屬性 PROPAGATION_REQUIRED
methodA(){
doSomeThingA();
seviceB.methodB();
doSomeThingB();
}
事務屬性 PROPAGATION_NEVER
methodB(){
……
}
單獨調(diào)用methodB,則非事務的執(zhí)行士八。 調(diào)用methodA則會拋出異常
g)PROPAGATION_NESTED:如果一個活動的事務存在容燕,則運行在一個嵌套的事務中. 如果沒有活動事務, 則按TransactionDefinition.PROPAGATION_REQUIRED 屬性執(zhí)行
這是一個嵌套事務,使用JDBC 3.0驅(qū)動時,僅僅支持DataSourceTransactionManager作為事務管理器。需要JDBC 驅(qū)動的java.sql.Savepoint類婚度。有一些JTA的事務管理器實現(xiàn)可能也提供了同樣的功能蘸秘。
使用PROPAGATION_NESTED,還需要把PlatformTransactionManager的nestedTransactionAllowed屬性設(shè)為true;
而nestedTransactionAllowed屬性值默認為false;
如下:
<bean id="system.platformTransactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="system.sessionFactory"/>
<property name="nestedTransactionAllowed" value="true"/>
</bean>
事務屬性 PROPAGATION_REQUIRED
methodA(){
doSomeThingA();
serviceB.methodB();
doSomeThingB();
}
事務屬性 PROPAGATION_NESTED
methodB(){
……
}
如果單獨調(diào)用methodB方法蝗茁,則按REQUIRED屬性執(zhí)行醋虏。
如果調(diào)用methodA方法,相當于下面的效果
main(){
Connection con = null;
Savepoint savepoint = null;
try{
con = getConnection();
con.setAutoCommit(false);
doSomeThingA();
//創(chuàng)造一個事務的保存點
savepoint = con2.setSavepoint();
try
methodB();
}catch(RuntimeException ex){
con.rollback(savepoint);
}finally{
//釋放資源
}
doSomeThingB();
con.commit();
}catch(RuntimeException ex){
con.rollback();
}finally{
//釋放資源
}
}
當methodB方法調(diào)用之前哮翘,調(diào)用setSavepoint方法颈嚼,保存當前的狀態(tài)到savepoint。如果methodB方法調(diào)用失敗忍坷,則恢復到之前保存的狀態(tài)粘舟。但是需要注意的是熔脂,這時的事務并沒有進行提交,如果后續(xù)的代碼doSomeThingB()方法調(diào)用失敗柑肴,則回滾包括methodB方法的所有操作霞揉。
嵌套事務一個非常重要的概念就是內(nèi)層事務依賴于外層事務。外層事務失敗時晰骑,會回滾內(nèi)層事務所做的動作适秩。而內(nèi)層事務操作失敗并不會引起外層事務的回滾。
PROPAGATION_NESTED 與PROPAGATION_REQUIRES_NEW的區(qū)別:它們非常類似,都像一個嵌套事務硕舆,如果不存在一個活動的事務秽荞,都會開啟一個新的事務。使用PROPAGATION_REQUIRES_NEW時抚官,內(nèi)層事務與外層事務就像兩個獨立的事務一樣扬跋,一旦內(nèi)層事務進行了提交后,外層事務不能對其進行回滾凌节。兩個事務互不影響钦听。兩個事務不是一個真正的嵌套事務。同時它需要JTA事務管理器的支持倍奢。
使用PROPAGATION_NESTED時朴上,外層事務的回滾可以引起內(nèi)層事務的回滾。而內(nèi)層事務的異常并不會導致外層事務的回滾卒煞,它是一個真正的嵌套事務痪宰。DataSourceTransactionManager使用savepoint支持PROPAGATION_NESTED時,需要JDBC 3.0以上驅(qū)動及1.4以上的JDK版本支持畔裕。其它的JTA TrasactionManager實現(xiàn)可能有不同的支持方式衣撬。
PROPAGATION_REQUIRED應該是我們首先的事務傳播行為。它能夠滿足我們大多數(shù)的事務需求柴钻。
事務的超時性淮韭、回滾和只讀
- 超時:
@Transactional(timeout=30) //默認是30秒
異常回滾:
指定單一異常類:@Transactional(rollbackFor=RuntimeException.class)
指定多個異常類:@Transactional(rollbackFor={RuntimeException.class, Exception.class})
該屬性用于設(shè)置需要進行回滾的異常類數(shù)組贴届,當方法中拋出指定異常數(shù)組中的異常時靠粪,則進行事務回滾。
正常的情況下也可以回滾:
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
- 只讀:
@Transactional(readOnly=true)
該屬性用于設(shè)置當前事務是否為只讀事務毫蚓,設(shè)置為true表示只讀占键,false則表示可讀寫,默認值為false元潘。