Spring的事務管理:html
事務本來是數據庫中的概念,在 Dao 層。但通常狀況下,須要將事務提高到業務層,即 Service 層。這樣作是爲了可以使用事務的特性來管理具體的業務。
在 Spring 中一般能夠經過如下三種方式來實現對事務的管理:
(1)使用 Spring 的事務代理工廠管理事務
(2)使用 Spring 的事務註解管理事務
(3)使用 AspectJ 的 AOP 配置管理事務
java
Spring事務管理API:程序員
Spring 的事務管理,主要用到兩個事務相關的接口。spring
(1)事務管理器接口
事務管理器是 PlatformTransactionManager 接口對象。其主要用於完成事務的提交、回滾,及獲取事務的狀態信息。查看 SpringAPI 幫助文檔:Spring 框架解壓目錄下的docs/javadoc-api/index.html。 sql
Modifier and Type | Method and Description |
---|---|
void |
commit(TransactionStatus status)
Commit the given transaction, with regard to its status.
|
TransactionStatus |
getTransaction(TransactionDefinition definition)
Return a currently active transaction or create a new one, according to the specified propagation behavior.
|
void |
rollback(TransactionStatus status)
Perform a rollback of the given transaction.
|
A、經常使用的兩個實現類
PlatformTransactionManager 接口有兩個經常使用的實現類:
DataSourceTransactionManager:使用 JDBC 或 iBatis 進行持久化數據時使用。
HibernateTransactionManager:使用 Hibernate 進行持久化數據時使用。
B、Spring 的回滾方式
Spring 事務的默認回滾方式是:發生運行時異常時回滾,發生受查異常時提交。不過,對於受查異常,程序員也能夠手工設置其回滾方式。數據庫
(2)事務定義接口:
事務定義接口 TransactionDefinition 中定義了事務描述相關的三類常量:事務隔離級別、事務傳播行爲、事務默認超時時限,及對它們的操做。 express
A、定義了五個事務隔離級別常量:
這些常量均是以 ISOLATION_開頭。即形如 ISOLATION_XXX。
DEFAULT:採用DB默認的事務隔離級別。MySql的默認爲REPEATABLE_READ; Oracle默認爲 READ_COMMITTED。
READ_UNCOMMITTED:讀未提交。未解決任何併發問題。
READ_COMMITTED:讀已提交。解決髒讀,存在不可重複讀與幻讀。
REPEATABLE_READ:可重複讀。解決髒讀、不可重複讀,存在幻讀
SERIALIZABLE:串行化。不存在併發問題。api
B、定義了七個事務傳播行爲常量
所謂事務傳播行爲是指,處於不一樣事務中的方法在相互調用時,執行期間事務的維護狀況。如,A 事務中的方法 doSome()調用 B 事務中的方法 doOther(),在調用執行期間事務的維護狀況,就稱爲事務傳播行爲。事務傳播行爲是加在方法上的。
事務傳播行爲常量都是以 PROPAGATION_ 開頭,形如 PROPAGATION_XXX。
REQUIRED:指定的方法必須在事務內執行。若當前存在事務,就加入到當前事務中;若當前沒有事務,則建立一個新事務。這種傳播行爲是最多見的選擇,也是Spring 默認的事務傳播行爲。
如該傳播行爲加在 doOther()方法上。若 doSome()方法在執行時就是在事務內的,則 doOther()方法的執行也加入到該事務內執行。若 doSome()方法沒有在事務內執行,則 doOther()方法會建立一個事務,並在其中執行。 數組
SUPPORTS:指定的方法支持當前事務,但若當前沒有事務,也能夠以非事務方式執行。 併發
MANDATORY:指定的方法必須在當前事務內執行,若當前沒有事務,則直接拋出異常。
REQUIRES_NEW:老是新建一個事務,若當前存在事務,就將當前事務掛起,直到新事務執行完畢。
NOT_SUPPORTED:指定的方法不能在事務環境中執行,若當前存在事務,就將當前事務掛起。
NEVER:指定的方法不能在事務環境下執行,若當前存在事務,就直接拋出異常。
NESTED:指定的方法必須在事務內執行。若當前存在事務,則在嵌套事務內執行;若當前沒有事務,則建立一個新事務。
C、定義了默認事務超時時限
常量 TIMEOUT_DEFAULT 定義了事務底層默認的超時時限,及不支持事務超時時限設置的 none 值。
注意,事務的超時時限起做用的條件比較多,且超時的時間計算點較複雜。因此,該值通常就使用默認值便可。
Spring事務代碼詳解:
要求:實現模擬購買股票。存在兩個實體:銀行帳戶 Account 與股票帳戶 Stock。當要購買股票時,須要從 Account 中扣除相應金額的存款,而後在 Stock 中增長相應的股票數量。而在這個過程當中,可能會拋出一個用戶自定義的異常。異常的拋出,將會使兩個操做回滾。
Step1:建立數據庫表 account、stock
Step2:建立實體類 Account 與 Stock (略)
Step3:定義 Dao 接口 IAccountDao 與 IStockDao
1 package com.tongji.dao; 2 3 public interface IAccountDao { 4 5 void insertAccount(String aname, double money); 6 7 void updateAccount(String aname, double money, boolean isBuy); 8 9 }
1 package com.tongji.dao; 2 3 public interface IStockDao { 4 5 void insertStock(String sname, int amount); 6 7 void updateStock(String sname, int amount, boolean isBuy); 8 9 }
Step4:定義 Dao 實現類 AccountDaoImpl 與 StockDaoImpl
1 package com.tongji.dao; 2 3 import org.springframework.jdbc.core.support.JdbcDaoSupport; 4 5 public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao { 6 7 @Override 8 public void insertAccount(String aname, double money) { 9 String sql = "insert into account(aname, balance) values(?,?)"; 10 this.getJdbcTemplate().update(sql, aname, money); 11 } 12 13 @Override 14 public void updateAccount(String aname, double money, boolean isBuy) { 15 String sql = "update account set balance=balance+? where aname=?"; 16 if (isBuy) { 17 sql = "update account set balance=balance-? where aname=?"; 18 } 19 this.getJdbcTemplate().update(sql, money, aname); 20 21 } 22 23 }
1 package com.tongji.dao; 2 3 import org.springframework.jdbc.core.support.JdbcDaoSupport; 4 5 public class StockDaoImpl extends JdbcDaoSupport implements IStockDao { 6 7 @Override 8 public void insertStock(String sname, int amount) { 9 String sql = "insert into stock(sname, count) values (?,?)"; 10 this.getJdbcTemplate().update(sql , sname, amount); 11 } 12 13 @Override 14 public void updateStock(String sname, int amount, boolean isBuy) { 15 //isBuy爲true,則表示購買股票,此時應增長股票帳戶中的股票數量 16 String sql = "update stock set count=count-? where sname=?"; 17 if (isBuy) { 18 sql = "update stock set count=count+? where sname=?"; 19 } 20 this.getJdbcTemplate().update(sql, amount, sname); 21 } 22 23 }
Step5:定義異常類 StockException
1 package com.tongji.beans; 2 3 public class StockException extends Exception { 4 private static final long serialVersionUID = 5377570098437361228L; 5 6 public StockException() { 7 super(); 8 } 9 10 public StockException(String message) { 11 super(message); 12 } 13 14 }
Step6:定義 Service 接口 IStockProcessService
1 package com.tongji.service; 2 3 public interface IStockProcessService { 4 void openAccount(String aname, double money); 5 void openStock(String sname, int amount); 6 void buyStock(String aname, double money, String sname, int amount); 7 }
Step7:定義 service 的實現類 StockProcessServiceImpl
1 package com.tongji.service; 2 3 import org.springframework.transaction.annotation.Isolation; 4 import org.springframework.transaction.annotation.Propagation; 5 import org.springframework.transaction.annotation.Transactional; 6 7 import com.tongji.beans.StockException; 8 import com.tongji.dao.IAccountDao; 9 import com.tongji.dao.IStockDao; 10 11 public class StockProcessServiceImpl implements IStockProcessService{ 12 private IAccountDao accountDao; 13 private IStockDao stockDao; 14 15 public void setAccountDao(IAccountDao accountDao) { 16 this.accountDao = accountDao; 17 } 18 19 public void setStockDao(IStockDao stockDao) { 20 this.stockDao = stockDao; 21 } 22 23 @Override 24 public void openAccount(String aname, double money) { 25 accountDao.insertAccount(aname, money); 26 } 27 28 @Override 29 public void openStock(String sname, int amount) { 30 stockDao.insertStock(sname, amount); 31 } 32 33 @Override 34 public void buyStock(String aname, double money, String sname, int amount) throws StockException { 35 boolean isBuy = true; 36 accountDao.updateAccount(aname, money, isBuy); 37 //故意拋出異常 38 if (true) { 39 throw new StockException("購買股票異常"); 40 } 41 stockDao.updateStock(sname, amount, isBuy); 42 } 43 44 }
Step8:定義Spring 配置文件
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" 5 http://www.springframework.org/schema/beans 6 http://www.springframework.org/schema/beans/spring-beans.xsd 7 http://www.springframework.org/schema/context 8 http://www.springframework.org/schema/context/spring-context.xsd"> 9 10 <!-- 註冊數據源:C3P0數據源 --> 11 <bean id="myDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> 12 <property name="driverClass" value="${jdbc.driverClass}" /> 13 <property name="jdbcUrl" value="${jdbc.url}" /> 14 <property name="user" value="${jdbc.user}" /> 15 <property name="password" value="${jdbc.password}" /> 16 </bean> 17 18 <!-- 註冊JDBC屬性文件 --> 19 <context:property-placeholder location="classpath:jdbc.properties"/> 20 21 <!-- 註冊Dao --> 22 <bean id="accountDao" class="com.tongji.dao.AccountDaoImpl"> 23 <property name="dataSource" ref="myDataSource"/> 24 </bean> 25 <bean id="stockDao" class="com.tongji.dao.StockDaoImpl"> 26 <property name="dataSource" ref="myDataSource"/> 27 </bean> 28 <!-- 註冊Service --> 29 <bean id="stockService" class="com.tongji.service.StockProcessServiceImpl"> 30 <property name="accountDao" ref="accountDao"/> 31 <property name="stockDao" ref="stockDao"/> 32 </bean> 33 </beans>
Step9:測試類
1 package com.tongji.test; 2 3 import org.junit.Before; 4 import org.junit.Test; 5 import org.springframework.context.ApplicationContext; 6 import org.springframework.context.support.ClassPathXmlApplicationContext; 7 8 import com.tongji.service.IStockProcessService; 9 10 public class MyTest { 11 12 private IStockProcessService service; 13 14 @Before 15 public void before() { 16 //建立容器 17 @SuppressWarnings("resource") 18 ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); 19 service = (IStockProcessService) ac.getBean("stockService"); 20 } 21 22 @Test 23 public void testOpen() { 24 service.openAccount("張三", 10000); 25 service.openStock("華爲", 5); 26 } 27 28 @Test 29 public void testBuyStock() { 30 service.buyStock("張三", 2000, "華爲", 5); 31 } 32 33 }
此配置文件沒有采用事務管理,因此購買股票的時候,出現了數據庫中帳戶金額減小了,可是股票數目沒有增長的不一致狀況。
使用 Spring 的事務代理工廠管理事務:
該方式是,須要爲目標類,即 Service 的實現類建立事務代理。事務代理使用的類是TransactionProxyFactoryBean,該類須要初始化以下一些屬性:
(1)transactionManager:事務管理器
(2)target:目標對象,即 Service 實現類對象
(3)transactionAttributes:事務屬性設置
對於 XML 配置代理方式實現事務管理時,受查異常的回滾方式,程序員能夠經過如下方式進行設置:經過「-異常」方式,可以使發生指定的異常時事務回滾;經過「+異常」方式,可以使發生指定的異常時事務提交。
修改Spring配置文件:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" 5 http://www.springframework.org/schema/beans 6 http://www.springframework.org/schema/beans/spring-beans.xsd 7 http://www.springframework.org/schema/context 8 http://www.springframework.org/schema/context/spring-context.xsd"> 9 10 <!-- 註冊數據源:C3P0數據源 --> 11 <bean id="myDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> 12 <property name="driverClass" value="${jdbc.driverClass}" /> 13 <property name="jdbcUrl" value="${jdbc.url}" /> 14 <property name="user" value="${jdbc.user}" /> 15 <property name="password" value="${jdbc.password}" /> 16 </bean> 17 18 <!-- 註冊JDBC屬性文件 --> 19 <context:property-placeholder location="classpath:jdbc.properties"/> 20 21 <!-- 註冊Dao --> 22 <bean id="accountDao" class="com.tongji.dao.AccountDaoImpl"> 23 <property name="dataSource" ref="myDataSource"/> 24 </bean> 25 <bean id="stockDao" class="com.tongji.dao.StockDaoImpl"> 26 <property name="dataSource" ref="myDataSource"/> 27 </bean> 28 <!-- 註冊Service --> 29 <bean id="stockService" class="com.tongji.service.StockProcessServiceImpl"> 30 <property name="accountDao" ref="accountDao"/> 31 <property name="stockDao" ref="stockDao"/> 32 </bean> 33 34 <!-- 事務 --> 35 <!-- 註冊事務管理器 --> 36 <bean id="myTxManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 37 <property name="dataSource" ref="myDataSource"/> 38 </bean> 39 <!-- 生成事務代理 --> 40 <bean id="stockServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> 41 <property name="transactionManager" ref="myTxManager"/> 42 <property name="target" ref="stockService"/> 43 <property name="transactionAttributes"> 44 <props> 45 <prop key="open*">ISOLATION_DEFAULT,PROPAGATION_REQUIRED</prop> 46 <prop key="buyStock">ISOLATION_DEFAULT,PROPAGATION_REQUIRED,-StockException</prop> 47 </props> 48 </property> 49 </bean> 50 </beans>
因爲本項目使用的是 JDBC 進行持久化,因此使用 DataSourceTransactionManager 類做爲事務管理器。
修改測試類:
public void before() {
//建立容器
@SuppressWarnings("resource")
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
service = (IStockProcessService) ac.getBean("stockServiceProxy");
}
使用 Spring 的事務註解管理事務:
經過@Transactional 註解方式,也可將事務織入到相應方法中。而使用註解方式,只需在配置文件中加入一個 tx 標籤,以告訴 spring 使用註解來完成事務的織入。該標籤只需指定一個屬性,事務管理器。
修改Spring配置文件:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:context="http://www.springframework.org/schema/context" 5 xmlns:aop="http://www.springframework.org/schema/aop" 6 xmlns:tx="http://www.springframework.org/schema/tx" 7 xsi:schemaLocation=" 8 http://www.springframework.org/schema/beans 9 http://www.springframework.org/schema/beans/spring-beans.xsd 10 http://www.springframework.org/schema/context 11 http://www.springframework.org/schema/context/spring-context.xsd 12 http://www.springframework.org/schema/tx 13 http://www.springframework.org/schema/tx/spring-tx.xsd 14 http://www.springframework.org/schema/aop 15 http://www.springframework.org/schema/aop/spring-aop.xsd"> 16 17 <!-- 註冊數據源:C3P0數據源 --> 18 <bean id="myDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> 19 <property name="driverClass" value="${jdbc.driverClass}" /> 20 <property name="jdbcUrl" value="${jdbc.url}" /> 21 <property name="user" value="${jdbc.user}" /> 22 <property name="password" value="${jdbc.password}" /> 23 </bean> 24 25 <!-- 註冊JDBC屬性文件 --> 26 <context:property-placeholder location="classpath:jdbc.properties"/> 27 28 <!-- 註冊Dao --> 29 <bean id="accountDao" class="com.tongji.dao.AccountDaoImpl"> 30 <property name="dataSource" ref="myDataSource"/> 31 </bean> 32 <bean id="stockDao" class="com.tongji.dao.StockDaoImpl"> 33 <property name="dataSource" ref="myDataSource"/> 34 </bean> 35 <!-- 註冊Service --> 36 <bean id="stockService" class="com.tongji.service.StockProcessServiceImpl"> 37 <property name="accountDao" ref="accountDao"/> 38 <property name="stockDao" ref="stockDao"/> 39 </bean> 40 41 <!-- 事務 --> 42 <!-- 註冊事務管理器 --> 43 <bean id="myTxManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 44 <property name="dataSource" ref="myDataSource"/> 45 </bean> 46 <!-- 開啓註解驅動 --> 47 <tx:annotation-driven transaction-manager="myTxManager"/> 48 </beans>
修改 service 的實現類 StockProcessServiceImpl:
1 package com.tongji.service; 2 3 import org.springframework.transaction.annotation.Isolation; 4 import org.springframework.transaction.annotation.Propagation; 5 import org.springframework.transaction.annotation.Transactional; 6 7 import com.tongji.beans.StockException; 8 import com.tongji.dao.IAccountDao; 9 import com.tongji.dao.IStockDao; 10 11 public class StockProcessServiceImpl implements IStockProcessService{ 12 private IAccountDao accountDao; 13 private IStockDao stockDao; 14 15 public void setAccountDao(IAccountDao accountDao) { 16 this.accountDao = accountDao; 17 } 18 19 public void setStockDao(IStockDao stockDao) { 20 this.stockDao = stockDao; 21 } 22 23 @Override 24 @Transactional(isolation=Isolation.DEFAULT, propagation=Propagation.REQUIRED) 25 public void openAccount(String aname, double money) { 26 accountDao.insertAccount(aname, money); 27 } 28 29 @Override 30 @Transactional(isolation=Isolation.DEFAULT, propagation=Propagation.REQUIRED) 31 public void openStock(String sname, int amount) { 32 stockDao.insertStock(sname, amount); 33 } 34 35 @Override 36 @Transactional(isolation=Isolation.DEFAULT, propagation=Propagation.REQUIRED, rollbackFor=StockException.class) 37 public void buyStock(String aname, double money, String sname, int amount) throws StockException { 38 boolean isBuy = true; 39 accountDao.updateAccount(aname, money, isBuy); 40 if (true) { 41 throw new StockException("購買股票異常"); 42 } 43 stockDao.updateStock(sname, amount, isBuy); 44 } 45 46 }
@Transactional 的全部可選屬性以下所示:
propagation:用於設置事務傳播屬性。該屬性類型爲 Propagation 枚舉,默認值爲Propagation.REQUIRED。
isolation:用於設置事務的隔離級別。該屬性類型爲 Isolation 枚舉,默認值爲Isolation.DEFAULT。
readOnly:用於設置該方法對數據庫的操做是不是隻讀的。該屬性爲 boolean,默認值爲 false。
timeout:用於設置本操做與數據庫鏈接的超時時限。單位爲秒,類型爲 int,默認值爲-1,即沒有時限。
rollbackFor:指定須要回滾的異常類。類型爲 Class[],默認值爲空數組。固然,若只有一個異常類時,能夠不使用數組。
rollbackForClassName:指定須要回滾的異常類類名。類型爲 String[],默認值爲空數組。固然,若只有一個異常類時,能夠不使用數組。
noRollbackFor:指定不須要回滾的異常類。類型爲 Class[],默認值爲空數組。固然,若只有一個異常類時,能夠不使用數組。
noRollbackForClassName:指定不須要回滾的異常類類名。類型爲 String[],默認值爲空數組。固然,若只有一個異常類時,能夠不使用數組。
須要注意的是,@Transactional 若用在方法上,只能用於 public 方法上。對於其餘非public 方法,若是加上了註解@Transactional,雖然 Spring 不會報錯,但不會將指定事務織入到該方法中。由於 Spring 會忽略掉全部非 public 方法上的@Transaction 註解。
若@Transaction 註解在類上,則表示該類上全部的方法均將在執行時織入事務。
使用 AspectJ 的 AOP 配置管理事務(重點):
使用 XML 配置事務代理的方式的不足是,每一個目標類都須要配置事務代理。當目標類較多,配置文件會變得很是臃腫。使用 XML 配置顧問方式能夠自動爲每一個符合切入點表達式的類生成事務代理。
修改Spring配置文件:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:context="http://www.springframework.org/schema/context" 5 xmlns:aop="http://www.springframework.org/schema/aop" 6 xmlns:tx="http://www.springframework.org/schema/tx" 7 xsi:schemaLocation=" 8 http://www.springframework.org/schema/beans 9 http://www.springframework.org/schema/beans/spring-beans.xsd 10 http://www.springframework.org/schema/context 11 http://www.springframework.org/schema/context/spring-context.xsd 12 http://www.springframework.org/schema/tx 13 http://www.springframework.org/schema/tx/spring-tx.xsd 14 http://www.springframework.org/schema/aop 15 http://www.springframework.org/schema/aop/spring-aop.xsd"> 16 17 <!-- 註冊數據源:C3P0數據源 --> 18 <bean id="myDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> 19 <property name="driverClass" value="${jdbc.driverClass}" /> 20 <property name="jdbcUrl" value="${jdbc.url}" /> 21 <property name="user" value="${jdbc.user}" /> 22 <property name="password" value="${jdbc.password}" /> 23 </bean> 24 25 <!-- 註冊JDBC屬性文件 --> 26 <context:property-placeholder location="classpath:jdbc.properties"/> 27 28 <!-- 註冊Dao --> 29 <bean id="accountDao" class="com.tongji.dao.AccountDaoImpl"> 30 <property name="dataSource" ref="myDataSource"/> 31 </bean> 32 <bean id="stockDao" class="com.tongji.dao.StockDaoImpl"> 33 <property name="dataSource" ref="myDataSource"/> 34 </bean> 35 <!-- 註冊Service --> 36 <bean id="stockService" class="com.tongji.service.StockProcessServiceImpl"> 37 <property name="accountDao" ref="accountDao"/> 38 <property name="stockDao" ref="stockDao"/> 39 </bean> 40 41 <!-- 事務 --> 42 <!-- 註冊事務管理器 --> 43 <bean id="myTxManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 44 <property name="dataSource" ref="myDataSource"/> 45 </bean> 46 <!-- 註冊事務通知 --> 47 <tx:advice id="txAdvice" transaction-manager="myTxManager"> 48 <tx:attributes> 49 <!-- 指定在鏈接點方法上應用的事務屬性 --> 50 <tx:method name="open*" isolation="DEFAULT" propagation="REQUIRED"/> 51 <tx:method name="buyStock" isolation="DEFAULT" propagation="REQUIRED" rollback-for="StockException"/> 52 </tx:attributes> 53 </tx:advice> 54 55 <!-- AOP配置 --> 56 <aop:config> 57 <!-- 指定切入點 --> 58 <aop:pointcut expression="execution(* *..service.*.*(..))" id="stockPointCut"/> 59 <aop:advisor advice-ref="txAdvice" pointcut-ref="stockPointCut"/> 60 </aop:config> 61 </beans>
總結:Spring 的事務管理,是 AOP 的應用,將事務做爲切面織入到了 Service 層的業務方法中。