1. 程式人生 > >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 的容器事務管理機制在這裡即體現出其強大
的能量。

★ 引數化配置的事務管理
在上面的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的模板實現,同樣,這些實現也為我們的開發
提供了強有力的支援。