1. 程式人生 > >12 Spring框架 SpringDAO的事務管理

12 Spring框架 SpringDAO的事務管理

http 定義 public strong 比較 attr on() platform attribute

上一節我們說過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&amp;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&amp;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的事務管理