方式一:使用org.springframework.transaction.interceptor.TransactionProxyFactoryBean
applicationContext.xml
測試代碼:
當updateUser中發生異常的時候,則兩條記錄都不會被插入。此方式實現的缺點,針對每個需要進行事務管理的bean都需要定義
TransactionProxyFactoryBean,造成較多的冗餘代碼。
方式二:
採用TransactionInterceptor通知和BeanNameAutoProxyCreator自動代理
applicationContext.xml
測試代碼:
聲明式事務又有三種實現方法:
1 (第一種) 最早的方法,用TransactionProxyFactoryBean,他是一個有AOP代理功能的FactoryBean.他返回的對象有事務.
還要在spring的配置文件XML中配置,比較麻煩,不詳細說.
- <!-- 事務測試DAO -->
- <bean id="go_TestPOAO" class="pic.dao.transaction_test.TestPOAOImpl" parent="go_POAOBase"></bean>
- <!-- 事務測試DAO 聲明式事務管理 -->
- <bean id="go_TestPOAOProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
- <property name="proxyInterfaces">
- <list>
- <value>pic.dao.transaction_test.TestPOAO</value>
- </list>
- </property>
- <property name="target" ref="go_TestPOAO"/>
- <property name="transactionManager" ref="transactionManager"/>
- <property name="transactionAttributes">
- <props>
- <prop key="insert*">PROPAGATION_REQUIRED</prop>
- </props>
- </property>
- </bean>
2 (第二種) 使用<tx:>來實現聲明式事務 ,也要在spring的配置文件XML中配置,比較麻煩,不詳細說.
- <tx:advice id="">
- .....
- </tx:advice>
- <aop:config>
- .....
- </aop:config>
3 (第三種) 這個方法方便,使用註解來實現聲明式事務, 下面詳細說說這個方法:
第一步:引入<tx:>命名空間 ,在spring的配置文件中修改, beans根元素裏多了三行,如下
- <?xml version="1.0" encoding="UTF-8"?>
- <beans
- xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:tx="http://www.springframework.org/schema/tx"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
- http://www.springframework.org/schema/tx
- http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
第二步:在spring的配置文件中修改,將所有具有@Transactional 註解的bean自動配置爲聲明式事務支持
- <!--JDBC事務管理器,根據你的情況使用不同的事務管理器,如果工程中有Hibernate,就用Hibernate的事務管理器 -->
- <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
- <property name="dataSource">
- <ref local="dataSource"/>
- </property>
- </bean>
- <!-- 用註解來實現事務管理 -->
- <tx:annotation-driven transaction-manager="transactionManager"/>
<!--JDBC事務管理器,根據你的情況使用不同的事務管理器,如果工程中有Hibernate,就用Hibernate的事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource">
<ref local="dataSource"/>
</property>
</bean>
<!-- 用註解來實現事務管理 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
第三步: 在接口或類的聲明處 ,寫一個@Transactional. 要是隻的接口上寫, 接口的實現類就會繼承下來.
接口的實現類的具體方法,還可以覆蓋類聲明處的設置.
- @Transactional
- public class TestPOAOImpl extends POAOBase implements TestPOAO
- {
- @Transactional(isolation = Isolation.READ_COMMITTED)
- public void test1()
- {
- String sql = "INSERT INTO sy_test (NAME,AGE) VALUES('註解趙雲',30)";
- execute(sql);
- sql = "INSERT INTO sy_test (NAME,AGE) VALUES('註解張飛',26)";
- execute(sql);
- int a = 9 / 0; //異常
- sql = "INSERT INTO sy_test (NAME,AGE) VALUES('註解關羽',33)";
- execute(sql);
- System.out.println("走完了");
- }
- //execute() 方法略...
- }
@Transactional
public class TestPOAOImpl extends POAOBase implements TestPOAO
{
@Transactional(isolation = Isolation.READ_COMMITTED)
public void test1()
{
String sql = "INSERT INTO sy_test (NAME,AGE) VALUES('註解趙雲',30)";
execute(sql);
sql = "INSERT INTO sy_test (NAME,AGE) VALUES('註解張飛',26)";
execute(sql);
int a = 9 / 0; //異常
sql = "INSERT INTO sy_test (NAME,AGE) VALUES('註解關羽',33)";
execute(sql);
System.out.println("走完了");
}
//execute() 方法略...
}
注意的幾點:
1 @Transactional 只能被應用到public方法上, 對於其它非public的方法,如果標記了@Transactional也不會報錯,但方法沒有事務功能.
2 默認情況下,一個有事務方法, 遇到RuntiomeException 時會回滾 . 遇到 受檢查的異常 是不會回滾 的. 要想所有異常都回滾,要加上 @Transactional( rollbackFor={Exception.class,其它異常}) .
@Transactional 的所有可選屬性如下:
屬性 | 類型 | 默認值 | 說明 |
propagation | Propagation枚舉 | REQUIRED | 事務傳播屬性 (下有說明) |
isolation | isolation枚舉 | DEFAULT | 事務隔離級別 (另有說明) |
readOnly | boolean | false | 是否只讀 |
timeout | int | -1 | 超時(秒) |
rollbackFor | Class[] | {} | 需要回滾的異常類 |
rollbackForClassName | String[] | {} | 需要回滾的異常類名 |
noRollbackFor | Class[] | {} | 不需要回滾的異常類 |
noRollbackForClassName | String[] | {} | 不需要回滾的異常類名 |
事務的隔離級別 有如下可選:
可以去看spring源碼 : org.springframework.transaction.annotation.Isolation
DEFAULT採用數據庫默認隔離級別
READ_UNCOMMITTED
READ_COMMITTED
REPEATABLE_READ
SERIALIZABLE
數據庫提供了四種事務隔離級別, 不同的隔離級別採用不同的鎖類開來實現.
在四種隔離級別中, Serializable的級別最高, Read Uncommited級別最低.
大多數數據庫的默認隔離級別爲: Read Commited,如Sql Server , Oracle.
少數數據庫默認的隔離級別爲Repeatable Read, 如MySQL InnoDB存儲引擎
即使是最低的級別,也不會出現 第一類 丟失 更新問題 .
- Read Uncommited :讀未提交數據( 會出現髒讀,不可重複讀,幻讀 ,避免了 第一類丟失 更新 )
- Read Commited :讀已提交的數據(會出現不可重複讀,幻讀)
- Repeatable Read :可重複讀(會出現幻讀)
- Serializable :串行化
丟失 更新 :
當兩個或多個事務選擇同一行,然後基於最初選定的值更新該行時,會發生丟失更新問題。每個事務都不知道其它事務的存在。最後的更新將重寫由其它事務所做的更新,這將導致數據丟失。
例:
事務A和事務B同時修改某行的值,
1.事務A將數值改爲1並提交
2.事務B將數值改爲2並提交。
這時數據的值爲2,事務A所做的更新將會丟失。
解決辦法:對行加鎖,只允許併發一個更新事務。
髒讀: 一個事務讀到另一個事務未提交的更新數據
例:
1.Mary的原工資爲1000, 財務人員將Mary的工資改爲了8000(但未提交事務)
2.Mary讀取自己的工資 ,發現自己的工資變爲了8000,歡天喜地!
3.而財務發現操作有誤,回滾了事務,Mary的工資又變爲了1000, 像這樣,Mary記取的工資數8000是一個髒數據。
不可重複讀: 在同一個事務中,多次讀取同一數據,返回的結果有所不同. 換句話說就是,後續讀取可以讀到另一個事務已提交的更新數據. 相反"可重複讀"在同一事務多次讀取數據時,能夠保證所讀數據一樣,也就是後續讀取不能讀到另一事務已提交的更新數據.
例:
1.在事務1中,Mary 讀取了自己的工資爲1000,操作並沒有完成
2.在事務2中,這時財務人員修改了Mary的工資爲2000,並提交了事務.
3.在事務1中,Mary 再次讀取自己的工資時,工資變爲了2000
解決辦法:如果只有在修改事務完全提交之後纔可以讀取數據,則可以避免該問題。
幻讀: 一個事務讀取到另一個事務已提交的insert數據.
例:
第一個事務對一個表中的數據進行了修改,這種修改涉及到表中的全部數據行。同時 (此時第一事務還未提交) ,第二個事務向表中插入一行新數據。這時第一個事務再去讀取表時,發現表中還有沒有修改的數據行,就好象發生了幻覺一樣。
事務的傳播屬性 ,有如下可選
可以去看spring源碼 : org.springframework.transaction.annotation.Propagation
REQUIRED | 業務方法需要在一個事務中運行,如果方法運行時,已處在一個事務中,那麼就加入該事務,否則自己創建一個新的事務.這是spring默認的傳播行爲. |
SUPPORTS | 如果業務方法在某個事務範圍內被調用,則方法成爲該事務的一部分,如果業務方法在事務範圍外被調用,則方法在沒有事務的環境下執行. |
MANDATORY | 只能在一個已存在事務中執行,業務方法不能發起自己的事務,如果業務方法在沒有事務的環境下調用,就拋異常 |
REQUIRES_NEW | 業務方法總是會爲自己發起一個新的事務,如果方法已運行在一個事務中,則原有事務被掛起,新的事務被創建,直到方法結束,新事務才結束,原先的事務纔會恢復執行. |
NOT_SUPPORTED | 聲明方法需要事務,如果方法沒有關聯到一個事務,容器不會爲它開啓事務.如果方法在一個事務中被調用,該事務會被掛起,在方法調用結束後,原先的事務便會恢復執行. |
NEVER | 聲明方法絕對不能在事務範圍內執行,如果方法在某個事務範圍內執行,容器就拋異常.只有沒關聯到事務,才正常執行. |
NESTED | 如果一個活動的事務存在,則運行在一個嵌套的事務中.如果沒有活動的事務,則按REQUIRED屬性執行.它使用了一個單獨的事務, 這個事務擁有多個可以回滾的保證點.內部事務回滾不會對外部事務造成影響, 它只對DataSourceTransactionManager 事務管理器起效. |