1. 程式人生 > 實用技巧 >Spring事務管理之幾種方式實現事務(轉)

Spring事務管理之幾種方式實現事務(轉)

一:事務認識

  大家所瞭解的事務Transaction,它是一些列嚴密操作動作,要麼都操作完成,要麼都回滾撤銷。Spring事務管理基於底層資料庫本身的事務處理機制。資料庫事務的基礎,是掌握Spring事務管理的基礎。這篇總結下Spring事務。

  事務具備ACID四種特性,ACID是Atomic(原子性)、Consistency(一致性)、Isolation(隔離性)和Durability(永續性)的英文縮寫。 

  (1)原子性(Atomicity)
    事務最基本的操作單元,要麼全部成功,要麼全部失敗,不會結束在中間某個環節。事務在執行過程中發生錯誤,會被回滾到事務開始前的狀態,就像這個事務從來沒有執行過一樣。

  (2)一致性(Consistency)
    事務的一致性指的是在一個事務執行之前和執行之後資料庫都必須處於一致性狀態。如果事務成功地完成,那麼系統中所有變化將正確地應用,系統處於有效狀態。如果在事務中出現錯誤,那麼系統中的所有變化將自動地回滾,系統返回到原始狀態。
  (3)隔離性(Isolation)
    指的是在併發環境中,當不同的事務同時操縱相同的資料時,每個事務都有各自的完整資料空間。由併發事務所做的修改必須與任何其他併發事務所做的修改隔離。事務檢視資料更新時,資料所處的狀態要麼是另一事務修改它之前的狀態,要麼是另一事務修改它之後的狀態,事務不會檢視到中間狀態的資料。
  (4)永續性(Durability)

    指的是隻要事務成功結束,它對資料庫所做的更新就必須永久儲存下來。即使發生系統崩潰,重新啟動資料庫系統後,資料庫還能恢復到事務成功結束時的狀態。


二:事務的傳播機制

  詳情看這個:事務的傳播機制

三:資料庫的4種隔離級別

  詳情看這個:資料庫的4種隔離級別


提示:

  spring的事務傳播行為是:REQUIRED

  mysql的預設的事務處理級別是:REPEATABLE-READ, 也就是可重複讀

  Oracle的預設的事務處理級別是:READ COMMITTED, 也就是讀已提交

可以通過更改資料庫的預設事務處理級別


四:事務幾種實現方式 (程式設計式事務管理、程式設計式事務管理)

 

  (1)程式設計式事務管理對基於 POJO 的應用來說是唯一選擇。我們需要在程式碼中呼叫beginTransaction()、commit()、rollback()等事務管理相關的方法,這就是程式設計式事務管理。
  (2)基於 TransactionProxyFactoryBean的宣告式事務管理
  (3)基於 @Transactional 的宣告式事務管理
  (4)基於Aspectj AOP配置事務
 注:此處側重講解宣告式事務,程式設計式事務在實際開發中得不到廣泛使用,僅供學習參考。


五:舉例說明事務不同實現

  以使用者購買股票為例   新建使用者物件、股票物件、以及dao、service層
/**
 * 賬戶物件
 *
 */
public class Account {
 
    private int accountid;
    private String name;
    private String balance;
    
    
    public int getAccountid() {
        return accountid;
    }
    public void setAccountid(int accountid) {
        this.accountid = accountid;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getBalance() {
        return balance;
    }
    public void setBalance(String balance) {
        this.balance = balance;
    }
}
/**
 * 股票物件
 *
 */
public class Stock {
 
    private int stockid;
    private String name;
    private Integer count;
    
    public Stock() {
        super();
    }
     
    public Stock(int stockid, String name, Integer count) {
        super();
        this.stockid = stockid;
        this.name = name;
        this.count = count;
    }
 
    public int getStockid() {
        return stockid;
    }
 
    public void setStockid(int stockid) {
        this.stockid = stockid;
    }
 
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getCount() {
        return count;
    }
    public void setCount(Integer count) {
        this.count = count;
    }
    
}

  DAO層

public interface AccountDao {
 
    void addAccount(String name,double money);
    
    void updateAccount(String name,double money,boolean isbuy);
    
}
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
 
    @Override
    public void addAccount(String name, double money) {
        String sql = "insert account(name,balance) values(?,?);";
        this.getJdbcTemplate().update(sql,name,money);
        
    }
 
    @Override
    public void updateAccount(String name, double money, boolean isbuy) {
        String sql = "update account set balance=balance+? where name=?";
        if(isbuy)
            sql = "update account set balance=balance-? where name=?";
        this.getJdbcTemplate().update(sql, money,name);
    }
    
}
public interface StockDao {
    
    void addStock(String sname,int count);
    
    void updateStock(String sname,int count,boolean isbuy);
 
}
public class StockDaoImpl extends JdbcDaoSupport implements StockDao {
 
    @Override
    public void addStock(String sname, int count) {
        String sql = "insert into stock(name,count) values(?,?)";
        this.getJdbcTemplate().update(sql,sname,count);
    }
 
    @Override
    public void updateStock(String sname, int count, boolean isbuy) {
        String sql = "update stock set count = count-? where name = ?";
        if(isbuy)
            sql = "update stock set count = count+? where name = ?";
        this.getJdbcTemplate().update(sql, count,sname);
    }
    
}

  Service層

public interface BuyStockService {
 
    public void addAccount(String accountname, double money);
    
    public void addStock(String stockname, int amount);
    
    public void buyStock(String accountname, double money, String stockname, int amount) throws BuyStockException;
    
}
public class BuyStockServiceImpl implements BuyStockService{
    
    private AccountDao accountDao;
    private StockDao stockDao;
    
    @Override
    public void addAccount(String accountname, double money) {
        accountDao.addAccount(accountname,money);
    }
 
    @Override
    public void addStock(String stockname, int amount) {
        stockDao.addStock(stockname,amount);
    }
 
    @Override
    public void buyStock(String accountname, double money, String stockname, int amount) throws BuyStockException {
        boolean isBuy = true;
        accountDao.updateAccount(accountname, money, isBuy);
        if(isBuy==true){
            throw new BuyStockException("購買股票發生異常");
        }
            stockDao.updateStock(stockname, amount, isBuy);
    }
 
    public AccountDao getAccountDao() {
        return accountDao;
    }
 
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }
 
    public StockDao getStockDao() {
        return stockDao;
    }
 
    public void setStockDao(StockDao stockDao) {
        this.stockDao = stockDao;
    }
    
}

  自定義異常類

public class BuyStockException extends Exception {
 
    public BuyStockException() {
        super();
    }
 
    public BuyStockException(String message) {
        super(message);
    }
 
}

(1)基於 TransactionProxyFactoryBean的宣告式事務管理

<?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:mvc="http://www.springframework.org/schema/mvc"
    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-4.0.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-aop-4.2.xsd
        ">
    
    <context:property-placeholder location="classpath:jdbc.properties"/>
    
    <!-- 註冊資料來源 C3P0 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"  >
         <property name="driverClass" value="${jdbc.driverClass}"></property>
         <property name="jdbcUrl"  value="${jdbc.url}"></property>
         <property name="user"  value="${jdbc.username}"></property>
         <property name="password" value="${jdbc.password}"></property>
    </bean>
    
    <bean id="accountDao" class="transaction.test2.dao.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <bean id="stockDao" class="transaction.test2.dao.StockDaoImpl">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <bean id="buyStockService" class="transaction.test2.service.BuyStockServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
        <property name="stockDao" ref="stockDao"></property>
    </bean>
    
    
    <!-- 事務管理器 -->
    
    <bean id="myTracnsactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    <!-- 事務代理工廠 -->
    <!-- 生成事務代理物件 -->
    <bean id="serviceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager" ref="myTracnsactionManager"></property>
        <property name="target" ref="buyStockService"></property>
        <property name="transactionAttributes">
            <props>
                <!-- 主要 key 是方法   
                    ISOLATION_DEFAULT  事務的隔離級別
                    PROPAGATION_REQUIRED  傳播行為
                -->
                <prop key="add*">ISOLATION_DEFAULT,PROPAGATION_REQUIRED</prop>
                <!-- -Exception 表示發生指定異常回滾,+Exception 表示發生指定異常提交 -->
                <prop key="buyStock">ISOLATION_DEFAULT,PROPAGATION_REQUIRED,-BuyStockException</prop>
            </props>
        </property>
        
    </bean>
    
    
</beans>  

  測試入口

public static void main(String[] args) {
        String resouce = "transaction/test2/applicationContext.xml";
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(resouce);
        BuyStockService buyStockService =  (BuyStockService) applicationContext.getBean("serviceProxy");
 
//        buyStockService.openAccount("小鄭", 5000);
        
//        buyStockService.openStock("知曉科技", 0);
        
        try {
            buyStockService.buyStock("小鄭", 1000, "知曉科技", 100);
        } catch (BuyStockException e) {
            e.printStackTrace();
        }
        
    }

  發生異常賬戶金額不能減,股票不能增加

(2)基於 @Transactional 的宣告式事務管理

  其他類不做改變,只改變購買股票介面實現類和配置檔案

public class BuyStockServiceImpl implements BuyStockService{
 
    private AccountDao accountDao;
    private StockDao stockDao;
    
    @Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED)
    @Override
    public void addAccount(String accountname, double money) {
        accountDao.addAccount(accountname,money);
        
    }
 
    @Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED)
    @Override
    public void addStock(String stockname, int amount) {
        stockDao.addStock(stockname,amount);
        
    }
 
    public BuyStockServiceImpl() {
        // TODO Auto-generated constructor stub
    }
    
    @Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED,rollbackFor=BuyStockException.class)
    @Override
    public void buyStock(String accountname, double money, String stockname, int amount) throws BuyStockException {
        boolean isBuy = true;
        accountDao.updateAccount(accountname, money, isBuy);
        if(isBuy==true){
            throw new BuyStockException("購買股票發生異常");
        }
            stockDao.updateStock(stockname, amount, isBuy);
        
    }
 
    public AccountDao getAccountDao() {
        return accountDao;
    }
 
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }
 
    public StockDao getStockDao() {
        return stockDao;
    }
 
    public void setStockDao(StockDao stockDao) {
        this.stockDao = stockDao;
    }
    
}
<context:property-placeholder location="classpath:jdbc.properties"/>
    
    <!-- 註冊資料來源 C3P0 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"  >
         <property name="driverClass" value="${jdbc.driverClass}"></property>
         <property name="jdbcUrl"  value="${jdbc.url}"></property>
         <property name="user"  value="${jdbc.username}"></property>
         <property name="password" value="${jdbc.password}"></property>
    </bean>
    
    <bean id="accountDao" class="transaction.test3.dao.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <bean id="stockDao" class="transaction.test3.dao.StockDaoImpl">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <bean id="buyStockService" class="transaction.test3.service.BuyStockServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
        <property name="stockDao" ref="stockDao"></property>
    </bean>
    
    
    <!-- 事務管理器 -->
    <bean id="myTracnsactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    <!-- 啟用事務註解 -->
    <tx:annotation-driven transaction-manager="myTracnsactionManager"/>

  可以看出,使用@Transactional註解的方式配置檔案要簡單的多,將事務交給事務註解驅動。它有個缺陷是他會把所有的連線點都作為切點將事務織入進去,顯然只需要在buyStock()方法織入事務即可。下面看看最後一種實現,它就可以精準的織入到指定的連線點

知識點:

事務超時:@Transactional(timeout=60)

  如果用這個註解描述一個方法的話,執行緒已經跑到方法裡面,如果已經過去60秒了還沒跑完這個方法並且執行緒在這個方法中的後面還有涉及到對資料庫的增刪改查操作時會報事務超時錯誤(會回滾)。   如果已經過去60秒了還沒跑完但是後面已經沒有涉及到對資料庫的增刪改查操作,那麼這時不會報事務超時錯誤(不會回滾)。 回滾:   Spring管理事務預設回滾的異常是什麼?    答案是RuntimeException或者Error。    注意:如果事務在try{}catch(Exceptione){e.printStackTrace();}中跑,並且catch中只是列印e的話,那麼事務不會rollback。因為異常被catch掉了,框架不知道發生了異常。   如果想要rollback,可以加上rollbackFor=Exception.class,然後:     ①在方法上新增throwsException,將方法中出現的異常丟擲給spring事務,     ②去掉方法體中的trycatch     ③catch(Exceptione){throwe;}繼續向上拋,目的是讓spring事務捕獲這個異常。       rollbackFor=Exception.class,catch(){        throw new RunTimeException();       }   如果不加rollbackFor=Exception.class,丟擲newException() 是不會回滾的。Spring原始碼如下:
    publicbooleanrollbackOn(Throwableex){     return(exinstanceofRuntimeException||exinstanceofError);     }   如果是RuntimeException或Error的話,就返回True,表示要回滾,否則返回False,表示不回滾。
  只有spring事務捕獲到Exception異常後,@Transactional(rollbackFor=Exception.class),才會起到應有的作用;catch(Exceptione){e.printStackTrace();}這句是捕獲try中出現的Exception然後將異常資訊打印出來,僅僅是打印出來,然後什麼也沒幹。
  @Transactional(timeout= 60,rollbackFor=Exception.class)與rollbackFor=Exception.class的作用是     1讓checked例外也回滾:在整個方法前加上@Transactional(rollbackFor=Exception.class)
    2讓unchecked例外不回滾:@Transactional(notRollbackFor=RunTimeException.class)
  checkedUnchecked exception是執行時錯誤。  

(3)基於Aspectj AOP配置事務

public class BuyStockServiceImpl implements BuyStockService{
 
    private AccountDao accountDao;
    private StockDao stockDao;
    
    @Override
    public void addAccount(String accountname, double money) {
        accountDao.addAccount(accountname,money);
        
    }
 
    @Override
    public void addStock(String stockname, int amount) {
        stockDao.addStock(stockname,amount);
        
    }
 
    public BuyStockServiceImpl() {
        // TODO Auto-generated constructor stub
    }
    
    @Override
    public void buyStock(String accountname, double money, String stockname, int amount) throws BuyStockException {
        boolean isBuy = true;
        accountDao.updateAccount(accountname, money, isBuy);
        if(isBuy==true){
            throw new BuyStockException("購買股票發生異常");
        }
            stockDao.updateStock(stockname, amount, isBuy);
        
    }
 
    public AccountDao getAccountDao() {
        return accountDao;
    }
 
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }
 
    public StockDao getStockDao() {
        return stockDao;
    }
 
    public void setStockDao(StockDao stockDao) {
        this.stockDao = stockDao;
    }
    
}
<context:property-placeholder location="classpath:jdbc.properties"/>
    
    <!-- 註冊資料來源 C3P0 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"  >
         <property name="driverClass" value="${jdbc.driverClass}"></property>
         <property name="jdbcUrl"  value="${jdbc.url}"></property>
         <property name="user"  value="${jdbc.username}"></property>
         <property name="password" value="${jdbc.password}"></property>
    </bean>
    
    <bean id="accountDao" class="transaction.test4.dao.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <bean id="stockDao" class="transaction.test4.dao.StockDaoImpl">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <bean id="buyStockService" class="transaction.test4.service.BuyStockServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
        <property name="stockDao" ref="stockDao"></property>
    </bean>
    
    
    <!-- 事務管理器 -->
    <bean id="myTracnsactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    <tx:advice id="txAdvice" transaction-manager="myTracnsactionManager">
        <tx:attributes>
            <!-- 為連線點指定事務屬性 -->
            <tx:method name="add*" isolation="DEFAULT" propagation="REQUIRED"/>
            <tx:method name="buyStock" isolation="DEFAULT" propagation="REQUIRED" rollback-for="BuyStockException"/>
        </tx:attributes>
    </tx:advice>
    
    <aop:config>
        <!-- 切入點配置 -->
        <aop:pointcut expression="execution(* *..service.*.*(..))" id="point"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="point"/>
    </aop:config>

  執行介面與其他方式一樣。

轉自:https://blog.csdn.net/chinacr07/article/details/78817449