1. 程式人生 > >Spring詳解(八)------事務管理

Spring詳解(八)------事務管理

目錄

 


  

1、事務介紹

  事務(Transaction),一般是指要做的或所做的事情。在計算機術語中是指訪問並可能更新資料庫中各種資料項的一個程式執行單元(unit)。

  這裡我們以取錢的例子來講解:比如你去ATM機取1000塊錢,大體有兩個步驟:第一步輸入密碼金額,銀行卡扣掉1000元錢;第二步從ATM出1000元錢。這兩個步驟必須是要麼都執行要麼都不執行。如果銀行卡扣除了1000塊但是ATM出錢失敗的話,你將會損失1000元;如果銀行卡扣錢失敗但是ATM卻出了1000塊,那麼銀行將損失1000元。

  如何保證這兩個步驟不會出現一個出現異常了,而另一個執行成功呢?事務就是用來解決這樣的問題。事務是一系列的動作,它們綜合在一起才是一個完整的工作單元,這些動作必須全部完成,如果有一個失敗的話,那麼事務就會回滾到最開始的狀態,彷彿什麼都沒發生過一樣。 在企業級應用程式開發中,事務管理是必不可少的技術,用來確保資料的完整性和一致性。

 

 

2、事務的四個特性(ACID)

  ①、原子性(Atomicity):事務是一個原子操作,由一系列動作組成。事務的原子性確保動作要麼全部完成,要麼完全不起作用。

  ②、一致性(Consistency):一旦事務完成(不管成功還是失敗),系統必須確保它所建模的業務處於一致的狀態,而不會是部分完成部分失敗。在現實中的資料不應該被破壞。

  ③、隔離性(Isolation):可能有許多事務會同時處理相同的資料,因此每個事務都應該與其他事務隔離開來,防止資料損壞。

  ④、永續性(Durability):一旦事務完成,無論發生什麼系統錯誤,它的結果都不應該受到影響,這樣就能從任何系統崩潰中恢復過來。通常情況下,事務的結果被寫到持久化儲存器中。

 

 

3、Spring 事務管理的核心介面

  首先我們建立一個Java工程,然後匯入 Spring 核心事務包

   

  我們開啟Spring的核心事務包,檢視如下類:org.springframework.transaction

  

   上面所示的三個類檔案便是Spring的事務管理介面。如下圖所示:下面我們分別對這三個介面進行簡單的介紹

  

 

4、 PlatformTransactionManager  事務管理器

   Spring事務管理器的介面是org.springframework.transaction.PlatformTransactionManager,如上圖所示,Spring並不直接管理事務,通過這個介面,Spring為各個平臺如JDBC、Hibernate等都提供了對應的事務管理器,也就是將事務管理的職責委託給Hibernate或者JTA等持久化機制所提供的相關平臺框架的事務來實現。

  我們進入到 PlatformTransactionManager 介面,檢視原始碼:

  

  

  ①、TransactionStatus getTransaction(TransactionDefinition definition) ,事務管理器 通過TransactionDefinition,獲得“事務狀態”,從而管理事務。

  ②、void commit(TransactionStatus status)  根據狀態提交

  ③、void rollback(TransactionStatus status) 根據狀態回滾

  也就是說Spring事務管理的為不同的事務API提供一致的程式設計模型,具體的事務管理機制由對應各個平臺去實現。

  比如下面我們匯入實現事務管理的兩種平臺:JDBC和Hibernate

  

  然後我們再次檢視PlatformTransactionManager介面,會發現它多了幾個實現類,如下:

  

 

 

5、TransactionStatus 事務狀態

  在上面 PlatformTransactionManager 介面中,有如下方法:

   

  這個方法返回的是 TransactionStatus物件,然後程式根據返回的物件來獲取事務狀態,然後進行相應的操作。

  而 TransactionStatus 這個介面的內容如下:

  

  這個介面描述的是一些處理事務提供簡單的控制事務執行和查詢事務狀態的方法,在回滾或提交的時候需要應用對應的事務狀態。

6、TransactionDefinition 基本事務屬性的定義

  上面講到的事務管理器介面PlatformTransactionManager通過getTransaction(TransactionDefinition definition)方法來得到事務,這個方法裡面的引數是TransactionDefinition類,這個類就定義了一些基本的事務屬性。 

那麼什麼是事務屬性呢?事務屬性可以理解成事務的一些基本配置,描述了事務策略如何應用到方法上。事務屬性包含了5個方面,如圖所示:

  

  TransactionDefinition 介面方法如下:

  

 

  一、傳播行為:當事務方法被另一個事務方法呼叫時,必須指定事務應該如何傳播。

    Spring 定義瞭如下七中傳播行為,這裡以A業務和B業務之間如何傳播事務為例說明:

  ①、PROPAGATION_REQUIRED :required , 必須。預設值,A如果有事務,B將使用該事務;如果A沒有事務,B將建立一個新的事務。

  ②、PROPAGATION_SUPPORTS:supports ,支援。A如果有事務,B將使用該事務;如果A沒有事務,B將以非事務執行。

  ③、PROPAGATION_MANDATORY:mandatory ,強制。A如果有事務,B將使用該事務;如果A沒有事務,B將拋異常。

  ④、PROPAGATION_REQUIRES_NEW :requires_new,必須新的。如果A有事務,將A的事務掛起,B建立一個新的事務;如果A沒有事務,B建立一個新的事務。

  ⑤、PROPAGATION_NOT_SUPPORTED :not_supported ,不支援。如果A有事務,將A的事務掛起,B將以非事務執行;如果A沒有事務,B將以非事務執行。

  ⑥、PROPAGATION_NEVER :never,從不。如果A有事務,B將拋異常;如果A沒有事務,B將以非事務執行。

  ⑦、PROPAGATION_NESTED :nested ,巢狀。A和B底層採用儲存點機制,形成巢狀事務。

 

  二、隔離級別:定義了一個事務可能受其他併發事務影響的程度。

  併發事務引起的問題:

    在典型的應用程式中,多個事務併發執行,經常會操作相同的資料來完成各自的任務。併發雖然是必須的,但可能會導致以下的問題。

    ①、髒讀(Dirty reads)——髒讀發生在一個事務讀取了另一個事務改寫但尚未提交的資料時。如果改寫在稍後被回滾了,那麼第一個事務獲取的資料就是無效的。

    ②、不可重複讀(Nonrepeatable read)——不可重複讀發生在一個事務執行相同的查詢兩次或兩次以上,但是每次都得到不同的資料時。這通常是因為另一個併發事務在兩次查詢期間進行了更新。

    ③、幻讀(Phantom read)——幻讀與不可重複讀類似。它發生在一個事務(T1)讀取了幾行資料,接著另一個併發事務(T2)插入了一些資料時。在隨後的查詢中,第一個事務(T1)就會發現多了一些原本不存在的記錄。

    注意:不可重複讀重點是修改,而幻讀重點是新增或刪除。

 

  在 Spring 事務管理中,為我們定義瞭如下的隔離級別:

  ①、ISOLATION_DEFAULT:使用後端資料庫預設的隔離級別

  ②、ISOLATION_READ_UNCOMMITTED:最低的隔離級別,允許讀取尚未提交的資料變更,可能會導致髒讀、幻讀或不可重複讀

  ③、ISOLATION_READ_COMMITTED:允許讀取併發事務已經提交的資料,可以阻止髒讀,但是幻讀或不可重複讀仍有可能發生

  ④、ISOLATION_REPEATABLE_READ:對同一欄位的多次讀取結果都是一致的,除非資料是被本身事務自己所修改,可以阻止髒讀和不可重複讀,但幻讀仍有可能發生

  ⑤、ISOLATION_SERIALIZABLE:最高的隔離級別,完全服從ACID的隔離級別,確保阻止髒讀、不可重複讀以及幻讀,也是最慢的事務隔離級別,因為它通常是通過完全鎖定事務相關的資料庫表來實現的

  上面定義的隔離級別,在 Spring 的 TransactionDefinition.class 中也分別用常量 -1,0,1,2,4,8表示。比如 ISOLATION_DEFAULT 的定義:

  

  

   三、只讀

   這是事務的第三個特性,是否為只讀事務。如果事務只對後端的資料庫進行該操作,資料庫可以利用事務的只讀特性來進行一些特定的優化。通過將事務設定為只讀,你就可以給資料庫一個機會,讓它應用它認為合適的優化措施。

 

  四、事務超時

  為了使應用程式很好地執行,事務不能執行太長的時間。因為事務可能涉及對後端資料庫的鎖定,所以長時間的事務會不必要的佔用資料庫資源。事務超時就是事務的一個定時器,在特定時間內事務如果沒有執行完畢,那麼就會自動回滾,而不是一直等待其結束。

 

  五、回滾規則

  事務五邊形的最後一個方面是一組規則,這些規則定義了哪些異常會導致事務回滾而哪些不會。預設情況下,事務只有遇到執行期異常時才會回滾,而在遇到檢查型異常時不會回滾(這一行為與EJB的回滾行為是一致的) 。但是你可以宣告事務在遇到特定的檢查型異常時像遇到執行期異常那樣回滾。同樣,你還可以宣告事務遇到特定的異常不回滾,即使這些異常是執行期異常。

 

 

7、Spring 程式設計式事務和宣告式事務的區別 

  程式設計式事務處理:所謂程式設計式事務指的是通過編碼方式實現事務,允許使用者在程式碼中精確定義事務的邊界。即類似於JDBC程式設計實現事務管理。管理使用TransactionTemplate或者直接使用底層的PlatformTransactionManager。對於程式設計式事務管理,spring推薦使用TransactionTemplate。

  宣告式事務處理:管理建立在AOP之上的。其本質是對方法前後進行攔截,然後在目標方法開始之前建立或者加入一個事務,在執行完目標方法之後根據執行情況提交或者回滾事務。宣告式事務最大的優點就是不需要通過程式設計的方式管理事務,這樣就不需要在業務邏輯程式碼中摻雜事務管理的程式碼,只需在配置檔案中做相關的事務規則宣告(或通過基於@Transactional註解的方式),便可以將事務規則應用到業務邏輯中。

  簡單地說,程式設計式事務侵入到了業務程式碼裡面,但是提供了更加詳細的事務管理;而宣告式事務由於基於AOP,所以既能起到事務管理的作用,又可以不影響業務程式碼的具體實現。

 

8、不用事務實現轉賬

  我們還是以轉賬為例項。不用事務看如何實現轉賬。在資料庫中有如下表 account ,內容如下:

  

  有兩個使用者 Tom 和 Marry 。他們初始賬戶餘額都為 10000。這時候我們進行如下業務:Tom 向 Marry 轉賬 1000 塊。那麼這在程式中可以分解為兩個步驟:

  ①、Tom 的賬戶餘額 10000 減少 1000 塊,剩餘 9000 塊。

  ②、Marry 的賬戶餘額 10000 增加 1000 塊,變為 11000塊。

  上面兩個步驟要麼都執行成功,要麼都不執行。我們通過 TransactionTemplate 程式設計式事務來控制:

 

  第一步:建立Java工程並匯入相應的 jar 包(這裡不用事務其實不需要這麼多jar包,為了後面的講解需要,我們一次性匯入所有的jar包)

  

   第二步:編寫 Dao 層

    AccountDao 介面:

package com.ys.dao;
 
public interface AccountDao {
    /**
     * 匯款
     * @param outer 匯款人
     * @param money 匯款金額
     */
    public void out(String outer,int money);
     
    /**
     * 收款
     * @param inner 收款人
     * @param money 收款金額
     */
    public void in(String inner,int money);
 
}

AccountDaoImpl 介面實現類

package com.ys.dao.impl;
 
import org.springframework.jdbc.core.support.JdbcDaoSupport;
 
import com.ys.dao.AccountDao;
 
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
 
    /**
     * 根據使用者名稱減少賬戶金額
     */
    @Override
    public void out(String outer, int money) {
        this.getJdbcTemplate().update("update account set money = money - ? where username = ?",money,outer);
    }
 
    /**
     * 根據使用者名稱增加賬戶金額
     */
    @Override
    public void in(String inner, int money) {
        this.getJdbcTemplate().update("update account set money = money + ? where username = ?",money,inner);
    }
 
}

第三步:實現 Service 層

    AccountService 介面

package com.ys.service;
 
public interface AccountService {
     
    /**
     * 轉賬
     * @param outer 匯款人
     * @param inner 收款人
     * @param money 交易金額
     */
    public void transfer(String outer,String inner,int money);
 
}

AccountServiceImpl 介面實現類

package com.ys.service.impl;
 
import com.ys.dao.AccountDao;
import com.ys.service.AccountService;
 
public class AccountServiceImpl implements AccountService{
 
    private AccountDao accountDao;
     
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }
    @Override
    public void transfer(String outer, String inner, int money) {
        accountDao.out(outer, money);
        accountDao.in(inner, money);
    }
 
}

 第四步:Spring 全域性配置檔案 applicationContext.xml

<?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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/aop
                           http://www.springframework.org/schema/aop/spring-aop.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd">   
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
     
    <bean id="accountDao" class="com.ys.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
     
    <bean id="accountService" class="com.ys.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
    </bean>
</beans>

第五步:測試

public class TransactionTest {
     
    @Test
    public void testNoTransaction(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        AccountService account = (AccountService) context.getBean("accountService");
        //Tom 向 Marry 轉賬1000
        account.transfer("Tom", "Marry", 1000);
    }
 
}

第六步:檢視資料庫表 account

  

  上面的結果和我們想的一樣,Tom 賬戶 money 減少了1000塊。而 Marry 賬戶金額增加了1000塊。

 

   這時候問題來了,比如在 Tom 賬戶 money 減少了1000塊正常。而 Marry 賬戶金額增加時發生了異常,實際應用中比如斷電(這裡我們人為構造除數不能為0的異常),如下:

  

  那麼這時候我們執行測試程式,很顯然會報錯,那麼資料庫是什麼情況呢?

  

  資料庫account :

  

  我們發現,程式執行報錯了,但是資料庫 Tom 賬戶金額依然減少了 1000 塊,但是 Marry 賬戶的金額卻沒有增加。這在實際應用中肯定是不允許的,那麼如何解決呢?

 

 

9、程式設計式事務處理實現轉賬(TransactionTemplate )

  上面轉賬的兩步操作中間發生了異常,但是第一步依然在資料庫中進行了增加操作。實際應用中不會允許這樣的情況發生,所以我們這裡用事務來進行管理。

  Dao 層不變,我們在 Service 層注入 TransactionTemplate 模板,因為是用模板來管理事務,所以模板需要注入事務管理器  DataSourceTransactionManager 。而事務管理器說到底還是用底層的JDBC在管理,所以我們需要在事務管理器中注入 DataSource。這幾個步驟分別如下:

  AccountServiceImpl 介面:

package com.ys.service.impl;
 
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
 
import com.ys.dao.AccountDao;
import com.ys.service.AccountService;
 
public class AccountServiceImpl implements AccountService{
 
    private AccountDao accountDao;
    private TransactionTemplate transactionTemplate;
     
    public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
        this.transactionTemplate = transactionTemplate;
    }
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }
    @Override
    public void transfer(final String outer,final String inner,final int money) {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus arg0) {
                accountDao.out(outer, money);
                //int i = 1/0;
                accountDao.in(inner, money);
            }
        });
    }
 
}

Spring 全域性配置檔案 applicationContext.xml:

<?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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/aop
                           http://www.springframework.org/schema/aop/spring-aop.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd">   
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
     
    <bean id="accountDao" class="com.ys.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
     
    <bean id="accountService" class="com.ys.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
        <property name="transactionTemplate" ref="transactionTemplate"></property>
    </bean>
     
    <!-- 建立模板 -->
    <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
        <property name="transactionManager" ref="txManager"></property>
    </bean>
     
    <!-- 配置事務管理器 ,管理器需要事務,事務從Connection獲得,連線從連線池DataSource獲得 -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
</beans>

測試檔案保持不變,可以分兩次測試,第一次兩次操作沒有發生異常,然後資料庫正常改變了。第二次操作中間發生了異常,發現數據庫內容沒變。

 

  如果大家有興趣也可以試試底層的PlatformTransactionManager來進行事務管理,我這裡給出主要程式碼:

//定義一個某個框架平臺的TransactionManager,如JDBC、Hibernate
        DataSourceTransactionManager dataSourceTransactionManager =
                new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(this.getJdbcTemplate().getDataSource()); // 設定資料來源
     
        DefaultTransactionDefinition transDef = new DefaultTransactionDefinition(); // 定義事務屬性
        transDef.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED); // 設定傳播行為屬性
        TransactionStatus status = dataSourceTransactionManager.getTransaction(transDef); // 獲得事務狀態
        try {
            // 資料庫操作
            accountDao.out(outer, money);
            int i = 1/0;
            accountDao.in(inner, money);
             
            dataSourceTransactionManager.commit(status);// 提交
        } catch (Exception e) {
            dataSourceTransactionManager.rollback(status);// 回滾
        }

 

 

10、宣告式事務處理實現轉賬(基於AOP的 xml 配置)  

  Dao 層和 Service 層與我們最先開始的不用事務實現轉賬保持不變。主要是 applicationContext.xml 檔案變化了。

  我們在 applicationContext.xml 檔案中配置 aop 自動生成代理,進行事務管理:

  ①、配置管理器

  ②、配置事務詳情

  ③、配置 aop

<?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"
       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/aop
                           http://www.springframework.org/schema/aop/spring-aop.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/tx
                           http://www.springframework.org/schema/tx/spring-tx.xsd">
                             
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
     
    <bean id="accountDao" class="com.ys.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
     
    <bean id="accountService" class="com.ys.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
    </bean>
     
    <!-- 1 事務管理器 -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
     
    <!-- 2 事務詳情(事務通知)  , 在aop篩選基礎上,比如對ABC三個確定使用什麼樣的事務。例如:AC讀寫、B只讀 等
        <tx:attributes> 用於配置事務詳情(屬性屬性)
            <tx:method name=""/> 詳情具體配置
                propagation 傳播行為 , REQUIRED:必須;REQUIRES_NEW:必須是新的
                isolation 隔離級別
    -->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="transfer" propagation="REQUIRED" isolation="DEFAULT"/>
        </tx:attributes>
    </tx:advice>
     
    <!-- 3 AOP程式設計,利用切入點表示式從目標類方法中 確定增強的聯結器,從而獲得切入點 -->
    <aop:config>
        <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.ys.service..*.*(..))"/>
    </aop:config>
</beans>

測試類這裡我們就不描述了,也是分有異常和無異常進行測試,發現與預期結果是吻合的。

 

11、宣告式事務處理實現轉賬(基於AOP的 註解 配置) 

  分為如下兩步:

  ①、在applicationContext.xml 配置事務管理器,將並事務管理器交予spring

  ②、在目標類或目標方法添加註解即可 @Transactional

  首先在 applicationContext.xml 檔案中配置如下:

<!-- 1 事務管理器 -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!-- 2 將管理器交予spring
        * transaction-manager 配置事務管理器
        * proxy-target-class
            true : 底層強制使用cglib 代理
    -->
    <tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/>

其次在目標類或者方法添加註解@Transactional。如果在類上新增,則說明類中的所有方法都新增事務,如果在方法上新增,則只有該方法具有事務

package com.ys.service.impl;
 
import javax.annotation.Resource;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
 
import com.ys.dao.AccountDao;
import com.ys.service.AccountService;
 
@Transactional(propagation=Propagation.REQUIRED , isolation = Isolation.DEFAULT)
@Service("accountService")
public class AccountServiceImpl implements AccountService{
    @Resource(name="accountDao")
    private AccountDao accountDao;
     
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }
    @Override
    public void transfer(String outer, String inner, int money) {
        accountDao.out(outer, money);
        //int i = 1/0;
        accountDao.in(inner, money);
    }
 
}