Spring2.0框架的事務處理有兩大類:
JdbcTemplate操作采用的是JDBC默認的AutoCommit模式,也就是說我們還無法保證數據操作的原子性(要么全部生效,要么全部無效),如:
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.update("UPDATE user SET age = 10 WHERE id = 'erica'"); jdbcTemplate.update("UPDATE user SET age = age+1 WHERE id = 'erica'");
由於采用了AutoCommit模式,第一個update操作完成之后被自動提交,數據庫中”erica”對應的記錄已經被更新,如果第二個操作失敗,我們無法使得整個事務回滾到最初狀態。對於這個例子也許無關緊要,但是對於一個金融帳務系統而言,這樣的問題將導致致命錯誤。
為了實現數據操作的原子性,我們需要在程序中引入事務邏輯,在JdbcTemplate中引入事務機制,在Spring中有兩種方式:
1 編碼式事務
2 聲明式事務
Spring支持編程式事務管理和聲明式事務管理。許多Spring框架的用戶選擇聲明式事務管理,因為這種方式和應用程序的關聯較少,因此更加符合輕量級容器的概念。聲明式事務管理要優於編程式事務管理,盡管在靈活性方面它弱於編程式事務管理,因為編程式事務允許你通過代碼控制業務。
事務分為全局事務和局部事務。全局事務由應用服務器管理,需要底層服務器JTA支持(如WebLogic、WildFly等)。局部事務和底層采用的持久化方案有關,例如使用JDBC進行持久化時,需要使用Connetion對象來操作事務;而采用Hibernate進行持久化時,需要使用Session對象來操作事務。
Spring提供了如下所示的事務管理器。
這些事務的父接口都是PlatformTransactionManager。
Spring的事務管理機制是一種典型的策略模式,PlatformTransactionManager代表事務管理接口,該接口定義了三個方法,該接口並不知道底層如何管理事務,但是它的實現類必須提供getTransaction()方法(開啟事務)、commit()方法(提交事務)、rollback()方法(回滾事務)的多態實現,這樣就可以用不同的實現類代表不同的事務管理策略。使用JTA全局事務策略時,需要底層應用服務器支持,而不同的應用服務器所提供的JTA全局事務可能存在細節上的差異,因此實際配置全局事務管理器是可能需要使用JtaTransactionManager的子類,如:WebLogicJtaTransactionManager(Oracle的WebLogic服務器提供)、UowJtaTransactionManager(IBM的WebSphere服務器提供)等。
一、編碼式事務
首先,進行以下配置,假設配置文件為(Application-Context.xml):
<beans> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName"> <value>net.sourceforge.jtds.jdbc.Driver</value> </property> <property name="url"> <value>jdbc:jtds:sqlserver://127.0.0.1:1433/Sample</value> </property> <property name="username"> <value>test</value> </property> <property name="password"> <value>changeit</value> </property> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransac tionManager"> <property name="dataSource"> <ref local="dataSource" /> </property> </bean> <bean id="userDAO" class="net.xiaxin.dao.UserDAO"> <property name="dataSource"> <ref local="dataSource" /> </property> <property name="transactionManager"> <ref local="transactionManager" /> </property> </bean> </beans>
配置中包含了三個節點:
● dataSource
這里我們采用了apache dbcp組件提供的DataSource實現,並為其配置了JDBC驅動、數據庫URL、用戶名和密碼等參數。
● transactionManager
針對JDBC DataSource類型的數據源,我們選用了DataSourceTransactionManager作為事務管理組件。
如果需要使用基於容器的數據源(JNDI),我們可以采用如下配置:
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName"> <value>jdbc/sample</value> </property> </bean> <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTrans actionManager" />
● userDAO
userDAO 申明了一個UserDAO Bean,並為其指定了dataSource和transactionManger資源。
UserDAO對應的代碼如下:
public class UserDAO { private DataSource dataSource; private PlatformTransactionManager transactionManager; public PlatformTransactionManager getTransactionManager() { return transactionManager; } public void setTransactionManager(PlatformTransactionManager transactionManager) { this.transactionManager = transactionManager; } public DataSource executeTestSource() { return dataSource; } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } public void insertUser() { TransactionTemplate tt = new TransactionTemplate(getTransactionManager()); tt.execute(new TransactionCallback() { public Object doInTransaction(TransactionStatus status) { JdbcTemplate jt = new JdbcTemplate(executeTestSource()); jt.update( "insert into users (username) values ('xiaxin');"); jt.update( "insert into users (id,username) values(2,'erica');"); return null; } }); }
}
可以看到,在insertUser方法中,我們引入了一個新的模板類:org.springframework.transaction.support.TransactionTemplate。
TransactionTemplate封裝了事務管理的功能,包括異常時的事務回滾,以及操作成功后的事務提交。和JdbcTemplate一樣,它使得我們無需在瑣碎的try/catch/finally代碼中徘徊。在doInTransaction中進行的操作,如果拋出未捕獲異常將被自動回滾,如果成功執行,則將被自動提交。
這里我們故意制造了一些異常來觀察數據庫操作是否回滾(通過在第二條語句中更新自增ID字段故意觸發一個異常):
編寫一個簡單的TestCase來觀察實際效果:
InputStream is = new FileInputStream("Application-Context.xml"); XmlBeanFactory factory = new XmlBeanFactory(is); UserDAO userDAO = (UserDAO) factory.getBean("userDAO"); userDAO.insertUser();
相信大家多少覺得上面的代碼有點凌亂,Callback類的編寫似乎也有悖於日常的編程習慣(雖然筆者覺得這一方法比較有趣,因為它巧妙的解決了筆者在早期自行開發數據訪問模板中曾經遇到的問題)。
如何進一步避免上面這些問題?Spring 的容器事務管理機制在這里即體現出其強大的能量。
二、聲明式事務
聲明式事務又有三種實現方法:
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>
采用聲明式事務
1、聲明式事務配置
* 配置SessionFactory
* 配置事務管理器
* 事務的傳播特性
* 那些類那些方法使用事務
2、編寫業務邏輯方法
* 繼承HibernateDaoSupport類,使用HibernateTemplate來持久化,HibernateTemplate是Hibernate session的封裝 * 默認的回滾是RuntimeException(包括繼承RuntimeException的子類),普通異常不回滾
* 在編寫業務邏輯方法時,最好將異常一直往上拋出,在呈現層處理(struts)
* spring的事務需要設置到業務方法上(事務邊界定義到Facade類上),不要添加到Dao上
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"/>
第三步: 在接口或類的聲明處 ,寫一個@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() 方法略... }
注意的幾點:
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[] | {} | 不需要回滾的異常類名 |