jdbctemplate與事務管理
JdbcTemplate與事務
上例中的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. 引數化配置的事務管理
下面就這兩種方式進行介紹。
程式碼控制的事務管理
首先,進行以下配置,假設配置檔案為(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 dhcp元件提供的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 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 的容器事務管理機制在這裡即體現出其強大的能量。 u 引數化配置的事務管理 在上面的Application-Context.xml增加一個事務代理(UserDAOProxy)配置,同時,由於事務由容器管理,UserDAO不再需要TransactionManager設定,將其移除: <bean id="UserDAOProxy" class="org.springframework.transaction.interceptor.Transac tionProxyFactoryBean"> <property name="transactionManager"> <ref bean="transactionManager" /> </property> <property name="target"> <ref local="userDAO" /> </property> <property name="transactionAttributes"> <props> <prop key="insert*">PROPAGATION_REQUIRED</prop> <prop key="*">PROPAGATION_REQUIRED,readOnly</prop> </props> </property> </bean> <bean id="userDAO" class="net.xiaxin.dao.UserDAO"> <property name="dataSource"> <ref local="dataSource" /> </property> </bean>
上面的配置中,UserDAOProxy節點配置了一個針對userDAO bean的事務代理(由target屬性指定)。
通過transactionAttributes屬性,我們指定了事務的管理策略,即對所有以insert開頭的方法進行事務管理,如果被管理方法丟擲異常,則自動回滾方法中的事務,如果成功執行,則在方法完成之後進行事務提交。另一方面,對於其他方法(通過萬用字元*表示),則進行只讀事務管理,以獲得更好的效能。
與之對應,UserDAO.insertUser的程式碼修改如下:
public void insertUser(RegisterInfo regInfo) {
JdbcTemplate jt = new JdbcTemplate(executeTestSource());
jt.update("insert into users (username) values ('xiaxin');");
jt.update("insert into users (id,username) values (2,'erica');");
}
測試程式碼修改如下:
InputStream is = new FileInputStream("Application-Context.xml");
XmlBeanFactory factory = new XmlBeanFactory(is);
//注意這裡須通過代理Bean"userDAOProxy"獲得引用,而不是直接getBean(“userDAO”)
//此外這裡還存在一個有關強制轉型的潛在問題,請參見Hibernate in Spring一節後
//關於強制轉型的補充描述。
UserDAO userDAO = (UserDAO) factory.getBean("userDAOProxy");
userDAO.insertUser();
可以看到,insertUser變得非常簡潔。資料邏輯清晰可見,對比前面程式碼控制的事務管理,以及傳統的JDBC操作,相信大家會有一些霍然開朗的感覺。
細心的讀者會說,這只不過將程式碼轉移到了配置檔案,並沒有減少太多的工作量。這點區別也許並不重要,從應用維護的角度而言,配置化的事務管理顯然更具優勢。何況,實際開發中,如果前期設計細緻,方法的事務特性確定之後一般不會發生大的變動,之後頻繁的維護過程中,我們只需面對程式碼中的資料邏輯即可。
上面我們結合JdbcTemplate介紹了Spring中的模板操作以及事務管理機制。Spring作為一個開放式的應用開發平臺。同時也針對其他元件提供了良好的支援。在持久層,Spring提供面向了Hibernate、ibatis和JDO的模板實現,同樣,這些實現也為我們的開發提供了強有力的支援