Spring之旅第六篇-事務管理

1、什麼是事務

什麼是事務(Transaction)?事務是數據庫中的概念,是指訪問並可能更新數據庫中各類數據項的一個程序執行單元(unit)。java

有個很是經典的轉帳問題:A向B轉款1000元,A轉出成功,扣除了A帳戶的1000元,但當系統準備向B帳戶增長1000元出現了故障,轉入失敗,可是A帳戶的金額已經扣除,這樣的結果是A帳戶憑空少了1000元,很明顯這樣是不行的,正確的作法應該是當B帳戶增長成功後,A帳戶的扣款才能生效。mysql

簡單總結一句話就是:事務邏輯上的一組操做,組成這組操做的各個邏輯單元,要麼一塊兒成功,要麼一塊兒失敗web

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

  • 原子性(Atomicity):事務是一個原子操做,由一系列動做組成。事務的原子性確保動做要麼所有完成,要麼徹底不起做用。spring

  • 一致性(Consistency):一旦事務完成(無論成功仍是失敗),系統必須確保它所建模的業務處於一致的狀態,而不會是部分完成部分失敗。在現實中的數據不該該被破壞。sql

  • 隔離性(Isolation):可能有許多事務會同時處理相同的數據,所以每一個事務都應該與其餘事務隔離開來,防止數據損壞。數據庫

  • 持久性(Durability):一旦事務完成,不管發生什麼系統錯誤,它的結果都不該該受到影響,這樣就能從任何系統崩潰中恢復過來。一般狀況下,事務的結果被寫到持久化存儲器中。編程

3、事務隔離(Isolation Level)

 事務隔離意味着對於某一個正在運行的事務來講,好像系統中只有這一個事務,其餘併發的事務都不存在同樣。大部分狀況下,不多使用徹底隔離的事務。但不徹底隔離的事務會帶來以下一些問題。mybatis

 髒數據(Dirty Read):一個事務讀到了另外一個事務的未提交的數據併發

 不可重讀(Unrepeatable Read):一個事務讀到了另外一個事務已經提交的 update 的數據致使屢次查詢結果不一致app

 幻讀(Phantom Read):一個事務讀到了另外一個事務已經提交的 insert 的數據致使屢次查詢結果不一致

 經過設置事務隔離級別解決這些讀的問題,事務隔離級別分別是:

 讀操做未提交(Read Uncommitted):說明一個事務在提交前,其變化對於其餘事務來講是可見的。這樣髒讀、不可重讀和幻讀都是容許的。當一個事務已經寫入一行數據但未提交,其餘事務都不能再寫入此行數據;可是,任何事務均可以讀任何數據。這個隔離級別使用排寫鎖實現(髒讀,不可重複讀,虛讀都有可能發生

 讀操做已提交(Read Committed):它使用臨時的共讀鎖和排寫鎖實現。這種隔離級別不容許髒讀,但不可重讀和幻讀是容許的(避免髒讀。可是不可重複讀和虛讀有可能發生

 可重讀(Repeatable Read):說明事務保證可以再次讀取相同的數據而不會失敗。此隔離級別不容許髒讀和不可重讀,但幻讀會出現(避免髒讀和不可重複讀.可是虛讀有可能發生

 可串行化(Serializable):提供最嚴格的事務隔離。這個隔離級別不容許事務並行執行,只容許串行執行。這樣,髒讀、不可重讀或幻讀均可發生(避免以上全部讀問題

 Mysql 默認:可重複讀

Oracle 默認:讀已提交

4、spring事務管理的核心接口

由於在不一樣平臺,操做事務的代碼各不相同.spring提供了一個接口: PlatformTransactionManager 接口

 

 

PlatformTransactionManager主要有三個方法

  • TransactionStatus getTransaction(TransactionDefinition definition) ,事務管理器 經過TransactionDefinition,得到「事務狀態」,從而管理事務。

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

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

 事務的狀態:

獲取事務時帶入的接口爲TransactionDefinition

隔離級別取值詳解:

  1. PROPAGATION_REQUIRED :required , 必須。默認值,A若是有事務,B將使用該事務;若是A沒有事務,B將建立一個新的事務。

  2. PROPAGATION_SUPPORTS:supports ,支持。A若是有事務,B將使用該事務;若是A沒有事務,B將以非事務執行。

  3. PROPAGATION_MANDATORY:mandatory ,強制。A若是有事務,B將使用該事務;若是A沒有事務,B將拋異常。

  4. PROPAGATION_REQUIRES_NEW :requires_new,必須新的。若是A有事務,將A的事務掛起,B建立一個新的事務;若是A沒有事務,B建立一個新的事務。

  5. PROPAGATION_NOT_SUPPORTED :not_supported ,不支持。若是A有事務,將A的事務掛起,B將以非事務執行;若是A沒有事務,B將以非事務執行。

  6. PROPAGATION_NEVER :never,從不。若是A有事務,B將拋異常;若是A沒有事務,B將以非事務執行。

  7. PROPAGATION_NESTED :nested ,嵌套。A和B底層採用保存點機制,造成嵌套事務。

實現該接口的有DataSourceTransactionManager和HibernateTransitionmanager

5、spring事務處理

spring的事務處理分爲編程式事務處理聲明式事務處理

編程式事務處理:所謂編程式事務指的是經過編碼方式實現事務,容許用戶在代碼中精肯定義事務的邊界。即相似於JDBC編程實現事務管理。管理使用TransactionTemplate或者直接使用底層的PlatformTransactionManager。對於編程式事務管理,spring推薦使用TransactionTemplate。

聲明式事務處理:管理創建在AOP之上的。其本質是對方法先後進行攔截,而後在目標方法開始以前建立或者加入一個事務,在執行完目標方法以後根據執行狀況提交或者回滾事務。聲明式事務最大的優勢就是不須要經過編程的方式管理事務,這樣就不須要在業務邏輯代碼中摻瑣事務管理的代碼,只需在配置文件中作相關的事務規則聲明(或經過基於@Transactional註解的方式),即可以將事務規則應用到業務邏輯中。

簡單地說,編程式事務侵入到了業務代碼裏面,可是提供了更加詳細的事務管理;而聲明式事務因爲基於AOP,因此既能起到事務管理的做用,又能夠不影響業務代碼的具體實現。

理論不少了,開始編碼吧,以轉帳做爲例子

 DAO帳戶接口:

//帳戶接口
public interface AccountDao {
    //轉出
    void out(String outer,Double money);
    //轉入
    void in(String in,Double money);
}

轉帳實現:

//轉帳實現
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao   {
    public void out(String outer, Double money) {
        this.getJdbcTemplate().update("update account set money = money - ? where username = ?",money,outer);
    }

    public void in(String in, Double money) {
        this.getJdbcTemplate().update("update account set money = money + ? where username = ?",money,in);
    }
}

Service接口:

public interface AccountService {
    void transfer(String from,String to,Double money);
}

Service實現

public class AccountServiceImpl implements AccountService {
    // 業務層注入 DAO:
    private AccountDao accountDao;

    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    public void transfer(String from, String to, Double money) {
        accountDao.out(from,money);
        accountDao.in(to,money);
    }

測試:

@Test
public void fun1(){
    ApplicationContext context = new ClassPathXmlApplicationContext("META-INF/applicationContext.xml");
    AccountService account = (AccountService) context.getBean("accountService");
    //張三 向 李四 轉帳1000
    account.transfer("zhangsan", "lisi", 1000d);
}

數據庫原先數據:

執行方法以後:

 

這個結果很正常,如今咱們讓程序出一些錯誤,在兩個操做之間增長一個異常

accountDao.out(from,money);
int i= 1/0;//異常操做
accountDao.in(to,money);

運行報錯:java.lang.ArithmeticException: / by zero

可是張三的帳戶仍是扣除了1000元,李四帳戶並未改動

5.1編程式事務處理實現轉帳

如今須要咱們來改造代碼,讓轉帳支持事務,改造以下:

在AccountServiceImpl注入事務管理,代碼以下:

public class AccountServiceImpl implements AccountService {
    // 業務層注入 DAO:
    private AccountDao accountDao;
    //注入事務管理
    private TransactionTemplate transactionTemplate;

    public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
        this.transactionTemplate = transactionTemplate;
    }
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    public void transfer(final String from,final String to, final Double money) {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus arg0) {
                accountDao.out(from, money);
                int i = 1/0;
                accountDao.in(to, money);
            }
        });
    }
}

配置文件也作相應修改:

<?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/context
         http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.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="123456"></property>
    </bean>

    <bean id="accountDao" class="com.yuanqinnan.transaction.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>

    </bean>

    <bean id="accountService" class="com.yuanqinnan.transaction.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>

 

 

測試方法不改,仍然報錯,可是張三的帳戶未被修改,說明事務生效

5.2 聲明式事務處理實現轉帳(基於AOP的 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" 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/context
         http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.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="123456"></property>
    </bean>

    <bean id="accountDao" class="com.yuanqinnan.transaction.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>

    </bean>

    <bean id="accountService" class="com.yuanqinnan.transaction.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.yuanqinnan.transaction.AccountServiceImpl.transfer(..))"/>
    </aop:config>
</beans>

測試數據無誤

5.3 聲明式事務處理實現轉帳(基於AOP的 註解 配置)

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" 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/context
         http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:component-scan base-package="com.yuanqinnan.transaction" ></context:component-scan>
<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="123456"></property>
</bean>
<bean id="accountDao" class="com.yuanqinnan.transaction.AccountDaoImpl">
    <property name="dataSource" ref="dataSource"></property>

</bean>

<bean id="accountService" class="com.yuanqinnan.transaction.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 將管理器交予spring
    * transaction-manager 配置事務管理器
    * proxy-target-class
        true : 底層強制使用cglib 代理
-->
<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/>
</beans>

AccountServiceImpl加上註解便可實現

@Transactional(propagation=Propagation.REQUIRED , isolation = Isolation.DEFAULT)
public class AccountServiceImpl implements AccountService {}

以上三種方式都可實現事務的管理,事務管理講完以後整個spring的入門總結也結束了,下面開始mybatis之旅

相關文章
相關標籤/搜索