12 Spring框架 SpringDAO的事務管理
上一節我們說過Spring對DAO的兩個支持分為兩個知識點,一個是jdbc模板,另一個是事務管理。
事務是數據庫中的概念,但是在一般情況下我們需要將事務提到業務層次,這樣能夠使得業務具有事務的特性,來管理業務。
例如:在銀行的轉賬系統中,張三轉賬給李四,需要完成從張三的賬戶上扣取指定金額並加到李四的賬戶上,這樣一個過程需要具有原子性,即要成功都成功,要失敗都失敗。轉賬的過程即兩個對賬戶更新,需要將事務提升到業務層次,使得兩個操作具有原子性!
對以上的實現,Spring的API中有兩個常用的接口我們會使用到:
分別是:PlatformTransactionManager 平臺事務管理接口,和TransactionDefinition事務定義接口。
(1)PlatformTransactionManager
平臺管理器接口裏面只定義了三個方法:
分別是:提交,回滾,獲得當前事務的狀態方法。
平臺管理器接口有兩個主要的實現類:
①DataSourceTransactionManager:使用jdbc或ibatis進行數據持久化時使用
②HibernateTransactionManager:使用Hibernate進行數據持久化時使用
Spring的默認回滾方式有兩種:一種是運行時錯誤進行回滾,另一種是發生受查異常時提交,但是對於受查異常我們可以手工設置其回滾方式。
(2)TransactionDefinition
事務定義接口裏面定義了五個事務隔離級別常量,七個事務傳播行為常量,和默認事務超時時限。(這裏就不具體介紹,AIP裏面都有詳細說明)
Spring提供了兩種管理事務的方式:
①事務代理工廠
②事務註解
Spring也整合了AspectJ的:
①AOP配置事務管理
我們來實現一個以下的功能,並在這個功能上來進行事務管理:
需求:銀行系統需要完成一個轉賬系統,完成兩個用戶之間的轉賬,轉出用戶減少指定的金額,轉入賬戶增加指定的金額。
項目環境:win7,eclipse,Junit4,Spring jdbc模板(上一節的環境)
項目目錄結構:
按照需求我們先寫一個service接口:
//轉賬服務 public interface TransferAccountsService { //開戶,這裏就以name為唯一的標識void createAccount(String name,int balance); //轉賬 void transferAccounts(String userA,int money,String userB); }
然後實現類:
public class TransferAccountServiceImpl implements TransferAccountsService{ //accountDao這個有Spring進行註入 AccountDao accountDao; //保留setter方法 public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } //開戶方法實現 @Override public void createAccount(String name, int balance) { accountDao.insert(name, balance); } //轉賬方法實現 @Override public void transferAccounts(String userA, int money, String userB) { accountDao.update(userA, money, true);//true 表示轉出 accountDao.update(userB, money, false);//false代表轉入 } }
然後就要完成DAO了,還是一個DAO接口:
//DAO接口 public interface AccountDao { //插入用戶 void insert(String name, int balance); //更新用戶 void update(String user, int money,boolean transfer); }
AccountDao 的實現類
//因為需要使用jdbc模板,所以需要繼承JdbcDaoSupport //如果對jdbc模板不熟悉的可以看一下我Spring框架11章的內容 public class AccountDaoImpl extends extends JdbcDaoSupport implements AccountDao{ @Override public void insert(String name, int balance) { String sql = "insert into account (name,balance) values(?,?)"; this.getJdbcTemplate().update(sql,name,balance); } //更新賬戶,如果transfer為true表示轉出,否則轉入 @Override public void update(String user, int money,boolean transfer) { String sql = "update account set balance=balance+? where name=?"; if(transfer) { sql = "update account set balance=balance-? where name=?"; } this.getJdbcTemplate().update(sql,money,user); }
然後是我的完整配置文件:
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 註冊c3p0數據源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql:///test?useUnicode=true&characterEncoding=utf8"/> <property name="user" value="root"/> <property name="password" value="123"/> </bean> <!-- 註冊jdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 註冊AccountDaoImpl --> <bean id="AccountDaoImpl" class="com.testSpring.Dao.AccountDaoImpl"> <property name="jdbcTemplate" ref="jdbcTemplate"></property> </bean> <!-- 註冊TransferAccountServiceImpl --> <bean id="transferAccout" class="com.testSpring.Service.TransferAccountServiceImpl"> <property name="accountDao" ref="AccountDaoImpl"></property> </bean> </beans>
完成以上的步驟我們就可以測試了:
測試:
public class Test01 { private TransferAccountsService service; @Before public void before() { ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); service = (TransferAccountsService)ac.getBean("transferAccout"); } //創建兩個用戶 // public void test01() { // service.createAccount("張三",10000); // service.createAccount("李四", 10000); // } @Test public void test02() { //service.transferAccounts("張三", 1000, "李四"); service.transferAccounts("李四",1000,"張三"); } }
上面test01中我們先創建了兩個用戶:張三,李四,並為他們預存了10000元人民幣;test02中我們分別從張三向李四和李四向張三轉賬1000元,通過查看數據庫,我們得知測試成功。
以上的代碼是通過我測試的,但是在應用過程中,難免會發生從張三的賬戶扣取了1000元,而李四的賬戶未增加金額(扣除和增加更新操作中出現了異常),所以我們來模擬這個場景:
我們創建一個異常:
//轉賬異常 public class TransferException extends Exception { private static final long serialVersionUID = 1L; public TransferException() { super(); } public TransferException(String message) { super(message); } }
並在service代碼中的兩個更新操作之間拋出:
public class TransferAccountServiceImpl implements TransferAccountsService{ AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } @Override public void createAccount(String name, int balance) { accountDao.insert(name, balance); } @Override public void transferAccounts(String userA, int money, String userB) throws TransferException { accountDao.update(userA, money, true);//true 表示轉出 if(1==1) { throw new TransferException("轉賬不成功"); } accountDao.update(userB, money, false);//false代表轉入 } }
這樣就會產生異常(更改後的代碼需要在service接口,service方法和test方法後throws異常)。
當產生異常的時候就會發生,張三賬戶余額少了,但是李四賬戶余額不變的情況。
我們就要使用Spring提供的途徑來解決這樣的問題:
(一)使用Spring提供的事務代理工廠bean來管理事務:
這個類名全稱為:TransactionProxyFactoryBean,是我們之前在AOP中使用的ProxyFactoryBean的子類,事務的管理其實就是利用AOP的方法來實現管理。
這樣我們按照AOP的思想,我們還需要一個切面類,這個類是我們自己寫嗎?不,這個類已經有了,就是我們上面說到的DataSourceTransactionManager,和HibernateTransactionManager,因為我們使用的是jdbc模板,所以這裏我們使用DataSourceTransactionManager。
說了那麽多不如我們具體看看到底是如何配置的:
<!--省略了上面的配置--> <!-- 註冊事務管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 生成Service的事務代理對象 --> <bean id="serviceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="target" ref="transferAccout"/> <property name="transactionManager" ref="transactionManager"/> <property name="transactionAttributes"> <props> <prop key="createAccount">ISOLATION_DEFAULT,PROPAGATION_REQUIRED</prop> <prop key="transferAccounts">ISOLATION_DEFAULT,PROPAGATION_REQUIRED</prop> </props> </property> </bean>
這裏的配置是不是和Spring的AOP配置有那麽點相似?
transactionAttributes事務屬性中填寫了相關的事務常量,裏面的ISOLATION_DEFAULT,PROPAGATION_REQUIRED是Spring默認使用的兩種:默認隔離級別,傳播需要。
上面我們指定了兩個方法:createAccount(創建用戶方法),transferAccounts(轉賬方法),都是默認事務屬性。
我們之前說過了,Spring的默認回滾方式有兩種:一種是運行時錯誤進行回滾,另一種是發生受查異常時提交。
因為我們自定義的異常是屬於受查異常,所以我們可以控制它進行回滾,只要我們再事務屬性指定上添加上這樣的代碼:
<property name="transactionAttributes"> <props> <prop key="createAccount">ISOLATION_DEFAULT,PROPAGATION_REQUIRED</prop> <prop key="transferAccounts">ISOLATION_DEFAULT,PROPAGATION_REQUIRED,-TransferException</prop> </props> </property> <!--在transferAccounts方法上加-TransferException,當發生TransferException異常的時候會進行回滾-->
當我們需要將受查異常都設置成回滾的時候,我們就可以將所有的方法所設置成這樣:
<prop key="*">ISOLATION_DEFAULT,PROPAGATION_REQUIRED,-Exception</prop>
這樣,所有的方法事務屬性都是默認的,且當發生受查異常時都會發生回滾。
當我們需要將發生的運行時錯誤進行提交的時候,就在方法的事務屬性後添加“+異常名”。
即“-”回滾,“+”提交
進行了以上的修改,我們再進行測試會發現虛擬機報異常(我們將所有的異常都向外面拋,最後虛擬機掛掉),但是張三和李四的賬戶余額都未發生改變,這樣就完成了我們對事務的管理。
(二)使用Spring的事務註解來管理事務
同樣,上面的xml配置事務管理器在實際開發中不太使用(因為後兩種用的比較頻繁),當我們需要為很多事務添加事務管理的時候,配置文件會變得很臃腫,所以我們可以使用註解的方式來實現我們事務管理器。
第一步:
環境:在上面例子的基礎上,我們需要更改下約束文件,我們到Spring的開發包中找到對應的事務約束:
(圖片可能不太清楚,具體的到spring-framework-4.1.6.RELEASE-dist/spring-framework-4.1.6.RELEASE/docs/spring-framework-reference/html/xsd-config.html 查看)
約束文件添加結束後我們再eclipse的preferences中的xml下添加約束標簽:
接下來我們將事務代理對象bean刪除(其他不變),加入:
<!-- 註解驅動,在使用之前要添加事務的約束 --> <tx:annotation-driven transaction-manager="transactionManager"/> <!--transactionManager為上面我們註冊的事務管理器-->
第二步:
添加註解,在我們需要添加事務的方法上添加Transactional註解:
上面的代碼和之前的測試代碼一毛一樣,在需要添加事務的方法上加上如上圖的註解:
@Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED,rollbackFor=TransferException.class)
這樣我們就可以測試了,但是這裏的getBean("transferAccout")
不再是之前的代理對象了,而是我們註冊的業務類。
public class Test01 { private TransferAccountsService service; @Before public void before() { ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); service = (TransferAccountsService)ac.getBean("transferAccout"); } //創建兩個用戶 // public void test01() { // service.createAccount("張三",10000); // service.createAccount("李四", 10000); // } @Test public void test02() throws TransferException { service.transferAccounts("張三", 1000, "李四"); //service.transferAccounts("李四",1000,"張三"); } }
這樣,我們Spring對事務的兩種管理就是這樣了(其實是一種)。
接下來我們看看AspectJ怎麽使用AOP思想來對事務進行管理:
(三) AspectJ的AOP配置管理事務
環境:
我們要使用AspectJ還是要導入Spring對AspectJ整合的jar包,還要導入AOP聯盟和SpringAOP的jar包。
這裏我們給出整個配置文件:
<?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: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.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 註冊c3p0數據源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql:///test?useUnicode=true&characterEncoding=utf8"/> <property name="user" value="root"/> <property name="password" value="123"/> </bean> <!-- 註冊jdbcTemplate --> <!-- <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> --> <!-- 註冊AccountDaoImpl--> <bean id="AccountDaoImpl" class="com.testSpring.Dao.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 註冊TransferAccountServiceImpl--> <bean id="transferAccout" class="com.testSpring.Service.TransferAccountServiceImpl"> <property name="accountDao" ref="AccountDaoImpl"></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="createAccount" isolation="DEFAULT" propagation="REQUIRED"/> <tx:method name="transferAccounts" isolation="DEFAULT" propagation="REQUIRED" rollback-for="TransferException"/> </tx:attributes> </tx:advice> <!-- AspectJ的AOP配置 --> <aop:config> <!-- 這裏和配置AspectJ時候一樣,配置切入點表達式 --> <aop:pointcut expression="execution(* *..Service.*.*(..))" id="pointCut"/> <!-- pointcut-ref裏面可以填寫切入點表達式,也可以填寫上一行代碼的pointCut --> <aop:advisor advice-ref="txadvice" pointcut-ref="pointCut"/> </aop:config> </beans>
因為這裏只是運用之前的AOP知識完成事務的管理,所以配置的具體流程就不再詳細的說明。
總結:
至此我們就完成了使用Spring(AspectJ)對事務的管理,當然我們這裏都是設置了默認隔離級別,和REQUIRED事務傳播行為,並且配置了一些對受查異常了Rollback操作,在具體的使用當中,我們應該靈活的使用這個隔離級別和事務傳播行為!
版權聲明:本文為博主原創文章,如需轉載請表明出處。 https://blog.csdn.net/qq_39266910/article/details/78826171
12 Spring框架 SpringDAO的事務管理