1.論事務管理的重要性
在任何一個使用Spring框架的web工程中,事務管理是一個必須要考慮的東西。首先來回顧一下事物的四大特性:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability)。關於這四大特性,這裏不做解釋,不明白的朋友可以百度一下。總之,只需要知道事務保證了,某段對數據庫執行insert、update、delete操作的代碼,要麼全部執行、要麼全都不執行這一點。在我們常見的業務有轉賬業務,比如賬目表,表名account,字段id(唯一標識)、name(用戶名)、money(餘額)。表裏面有兩條記錄:01(id)、AA(name)、1000(money);02(id)、BB(name)、1000(money),現在AA向BB轉賬200,需要根據ID更新AA的餘額,減少200,;更新BB的餘額,增加200。這兩個更新操作要麼全都執行,要麼全都不執行。爲了滿足這個業務需求,就需要使用Spring的事務管理機制。
2.Spring 事務管理的三種方法
在進行事務管理時,先搭建一個簡單的web工程,模擬一個開發中遇到一個轉賬情景。需要引入的jar包如下
文件目錄如下圖:
applicationContext.xml文件的主要內容如下
<span style="font-weight: normal;"><beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
<!-- 引用外部的屬性文件 -->
<context:property-placeholder location="classpath:jdbc.properties" />
<!-- 配置c3p0連接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 配置Service層類 -->
<bean id="accoutService" class="cn.spring.transaction.demo1.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 配置Dao層類 -->
<bean id="accountDao" class="cn.spring.transaction.demo1.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans></span>
業務層的AccountServiceImpl實現類只有一個方法如下
<span style="font-weight: normal;">package cn.spring.transaction.demo1;
public class AccountServiceImpl implements AccountService {
// 注入Dao類
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
/**
* 轉賬接口
* @param outName 轉出賬戶
* @param inName 轉入賬戶
* @param money 轉賬金額
*/
@Override
public void transfer(String outName, String inName, Double money) {
accountDao.outMoney(outName, money);
accountDao.inMoney(inName, money);
}
}</span>
持久層的AccountDaoImpl實現類有兩個方法,一個是轉出方法,另一個是轉入方法,代碼如下:
<span style="font-weight: normal;"><span style="font-size:12px;">package cn.spring.transaction.demo1;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
/**
* 轉賬的DAO接口的實現類
* @author wuqinghai
*
*/
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
/**
* 轉出
* @param out 轉出賬戶
* @param money 轉出金額
*/
@Override
public void outMoney(String outName, Double money) {
String sql = "update account set money = money - ? where name = ?";
this.getJdbcTemplate().update(sql, money, outName);
}
/**
* 轉入
* @param inName 轉入賬戶
* @param money 轉入金額
*/
@Override
public void inMoney(String inName, Double money) {
String sql = "update account set money = money + ? where name = ?";
this.getJdbcTemplate().update(sql, money, inName);
}
}</span></span>
相應的數據庫的sql語句<span style="font-weight: normal;">CREATE TABLE `account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
`money` double DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
INSERT INTO `account` VALUES ('1', 'AA', '1000');
INSERT INTO `account` VALUES ('2', 'BB', '1000');
INSERT INTO `account` VALUES ('3', 'CC', '1000'); </span>
(1). 編程式事務管理
在自己的Service中使用TransactionTemplate,然後TransactionTemplate依賴DataSourceTransactionManager,最後DataSourceTransactionManager依賴注入DataSource。在applicationContext.xml文件裏添加相關類的注入如下的bean
<span style="white-space:pre"> </span><!-- 配置事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置事務管理器的模板: Spring爲簡化事務管理的代碼而提供的類 -->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager" />
</bean>
因爲數據庫的連接使用的是JDBC,所以使用DataSourceTransactionManager類管理事務,如果使用的是hibernate,則使用hibernate相關的事務管理類。編程式事務管理,顧名思義,需要在自己的代碼裏編寫相關的代碼來進行事務的管理。修改後的transfer方法代碼如下。 // 注入Dao類
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
// 注入事務管理的模板
private TransactionTemplate transactionTemplate;
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
/**
* 轉賬接口
* @param outName 轉出賬戶
* @param inName 轉入賬戶
* @param money 轉賬金額
*/
@Override
public void transfer(final String outName, final String inName, final Double money) {
transactionTemplate.execute(new TransactionCallbackWithoutResult(){
@Override
protected void doInTransactionWithoutResult(TransactionStatus arg0) {
accountDao.outMoney(outName, money);
accountDao.inMoney(inName, money);
}});
}
由此看見,編程式的事務管理在實際開發中相當繁瑣需要在添加事務管理的代碼處,編寫事務管理的代碼。在實際開發中很少很少使用該方式進行事務管理。
(2). 聲明式事務管理
相對於編程式的事務管理方法,聲明式的事務管理在實際開發中應用普遍。第一種使用代理的方式進行事務管理。該方式將目標類進行了增強,使得該類具備了事務管理的功能。實現這種方式只需要在applicationContext.xml文件裏配置Service類的代理即可。代碼如下
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
<!-- 引用外部的屬性文件 -->
<context:property-placeholder location="classpath:jdbc.properties" />
<!-- 配置c3p0連接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 配置Service層類 -->
<bean id="accoutService" class="cn.spring.transaction.demo1.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 配置Dao層類 -->
<bean id="accountDao" class="cn.spring.transaction.demo1.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置業務層的代理類 -->
<bean id="accoutServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!-- 配置目標對象 -->
<property name="target" ref="transactionManager" />
<!-- 注入事務管理器 -->
<property name="transactionManager" ref="transactionManager"/>
<!-- 注入事務的屬性 -->
<property name="transactionAttributes">
<props>
<!-- prop的格式:key屬性填寫的目標類裏需要事務管理的方法,其value值有一下五中
* PROPAGATION : 事務的傳播行爲
* ISOLATION : 事務的隔離級別
* readOnly : 只讀
* -Exception : 發生哪些異常回滾事務
* -Exception : 發生哪些異常不會滾事務
-->
<prop key="transfer">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
</beans>
這樣目標類AccountServiceImpl的transfer方法就具備了事務管理的能力。這裏需要注意的是在controller注入該Service類時,需要注入該類的代理對象accountServiceProxy。
第二種使用Spring中的切面原理配置事務管理,具體的配置內容如下
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
<!-- 引用外部的屬性文件 -->
<context:property-placeholder location="classpath:jdbc.properties" />
<!-- 配置c3p0連接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 配置Service層類 -->
<bean id="accoutService" class="cn.spring.transaction.demo1.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 配置Dao層類 -->
<bean id="accountDao" class="cn.spring.transaction.demo1.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置事務的通知:(事務的增強) -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="transfer" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 配置切面 -->
<aop:config>
<!-- 配置切入點 -->
<aop:pointcut expression="execution(* cn.spring.transaction.demo1.AccountService+.*(..))" id="pointcut1"/>
<!-- 配置切面 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>
</aop:config>
</beans>
第三種是基於註解的事務管理配置,這種是項目中最常用的一種,具體配置如下<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
<!-- 引用外部的屬性文件 -->
<context:property-placeholder location="classpath:jdbc.properties" />
<!-- 配置c3p0連接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 配置Service層類 -->
<bean id="accoutService" class="cn.spring.transaction.demo1.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 配置Dao層類 -->
<bean id="accountDao" class="cn.spring.transaction.demo1.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 開啓註解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
這樣配置完成後,只需要在需要事務管理的類上加上@Transactional註解即可。