1. 程式人生 > >spring學習(八)事務操作

spring學習(八)事務操作

一、事務的概念:

  事務是併發控制的單位,一系列操作組成的工作單元,該工作單元內的操作是不可分割的,也就是事務具有原子性,一個事務中的一系列的操作要麼全部成功,要麼一個都不做,所有操作必須成功完成,否則在每個操作中所作的所有更改都會被撤消。 
  事務的結束有兩種,當事務中的所以步驟全部成功執行時,事務提交。如果其中一個步驟失敗,將發生回滾操作,撤消撤消之前到事務開始時的所以操作。

1、事務的特性與屬性

⑴ 原子性(Atomicity)

  原子性是指事務包含的所有操作要麼全部成功,要麼全部失敗回滾,這和前面兩篇部落格介紹事務的功能是一樣的概念,因此事務的操作如果成功就必須要完全應用到資料庫,如果操作失敗則不能對資料庫有任何影響。

⑵ 一致性(Consistency)

  一致性是指事務必須使資料庫從一個一致性狀態變換到另一個一致性狀態,也就是說一個事務執行之前和執行之後都必須處於一致性狀態。

  拿轉賬來說,假設使用者A和使用者B兩者的錢加起來一共是5000,那麼不管A和B之間如何轉賬,轉幾次賬,事務結束後兩個使用者的錢相加起來應該還得是5000,這就是事務的一致性。

⑶ 隔離性(Isolation)

  隔離性是當多個使用者併發訪問資料庫時,比如操作同一張表時,資料庫為每一個使用者開啟的事務,不能被其他事務的操作所幹擾,多個併發事務之間要相互隔離

  即要達到這麼一種效果:對於任意兩個併發的事務T1和T2,在事務T1看來,T2要麼在T1開始之前就已經結束,要麼在T1結束之後才開始,這樣每個事務都感覺不到有其他事務在併發地執行。

  關於事務的隔離性資料庫提供了多種隔離級別,稍後會介紹到。

⑷ 永續性(Durability)

  永續性是指一個事務一旦被提交了,那麼對資料庫中的資料的改變就是永久性的,即便是在資料庫系統遇到故障的情況下也不會丟失提交事務的操作。

 

2、事務的併發性問題

(1)、髒讀:一個事務看到了另一個事務未提交的更新資料;當事務讀取尚未提交的資料時,就會發生這種情況。

  例如事務A正在訪問資料,並且對資料進行了修改,更改了一行資料,這種修改還沒有提交到資料庫中,而事務B在事務A提交更新之前讀取了已更新的行,然後使用了這個資料。 
如果事務A回滾該更新,則事務B使用的這個資料由於還沒有提交,就會被認為是不曾存在的資料,即為髒資料,根據髒資料所做的操作可能是不正確的。

(2)、 不可重複讀:在同一事務中,兩次讀取同一資料,得到內容不同,也就是有其他事務更改了這些資料;

  例如事務A查詢一條記錄後,事務B更新了事務A查詢的記錄,並提交了事務,那事務A再次查詢上次的記錄時,對同一資料查詢了兩次,得到的結果不同,這稱為不可重複讀。

(3)、幻讀:一個事務在執行過程中讀取到了另一個事務已提交的插入資料;

  即在第一個事務開始時讀取到一批資料,但此後另一個事務又插入了新資料並提交,此時第一個事務又讀取這批資料但發現多了一條,即好像發生幻覺一樣。

 

3、事務的隔離級別

 

現在來看看MySQL資料庫為我們提供的四種隔離級別:

 

  ① Serializable (序列化):可避免髒讀、不可重複讀、幻讀的發生。

 

  ② Repeatable read (可重複讀):可避免髒讀、不可重複讀的發生。

 

  ③ Read committed (讀已提交):可避免髒讀的發生。

 

  ④ Read uncommitted (讀未提交):最低級別,任何情況都無法保證。

 4、事務的傳播行為

  決定業務方法之間呼叫,事務應該如何處理

  事務傳播行為用來描述由某一個事務傳播行為修飾的方法被巢狀進另一個方法的時事務如何傳播。

事務傳播行為型別 說明
PROPAGATION_REQUIRED 如果當前沒有事務,就新建一個事務,如果已經存在一個事務中,加入到這個事務中。這是最常見的選擇。
PROPAGATION_SUPPORTS 支援當前事務,如果當前沒有事務,就以非事務方式執行。
PROPAGATION_MANDATORY 使用當前的事務,如果當前沒有事務,就丟擲異常。
PROPAGATION_REQUIRES_NEW 新建事務,如果當前存在事務,把當前事務掛起。
PROPAGATION_NOT_SUPPORTED 以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
PROPAGATION_NEVER 以非事務方式執行,如果當前存在事務,則丟擲異常。
PROPAGATION_NESTED 如果當前存在事務,則在巢狀事務內執行。如果當前沒有事務,則執行與PROPAGATION_REQUIRED類似的操作。

5、是否只讀

  1、true

  2、false

 

二、spring封裝了事務管理的程式碼

  1、開啟事務

  2、提交事務

  3、回滾事務

 

  由於不同的平臺操作事務的程式碼各不相同(JDBC、Mybatis、Hibernate等),所以spring提供了一個介面platformTransactionManager,不同平臺操作有其對應的實現類,例如:JDBCTransactionManager、HibernateTransactionManager

  在spring中最核心的操作事務的物件TransactionManager

 

 三、spring中操作資料庫的事務例項:

 

 

 導包:(匯入maven依賴)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>demojdbc</groupId>
    <artifactId>demojdbc</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.2.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>4.2.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>4.2.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.38</version>
        </dependency>
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>

    </dependencies>
</project>

 

(1)、書寫與資料庫對應的bean類

 1 package dyh.bean;
 2 
 3 /**
 4  * Created by Duyahui on 2018/12/16.
 5  */
 6 public class Money {
 7     private String name;
 8     private Integer money;
 9 
10     public String getName() {
11         return name;
12     }
13 
14     public void setName(String name) {
15         this.name = name;
16     }
17 
18     public Integer getMoney() {
19         return money;
20     }
21 
22     public void setMoney(Integer money) {
23         this.money = money;
24     }
25 
26     @Override
27     public String toString() {
28         return "Money{" +
29                 "name='" + name + '\'' +
30                 ", money=" + money +
31                 '}';
32     }
33 }

(2)、書寫持久層介面以及其實現類

public interface MoneyDao {
    //加錢
    void increaseMoney(Integer id,Double money);
    //減錢
    void decreaseMoney(Integer id,Double money);
}
public class MoneyDaoImpl extends JdbcDaoSupport implements MoneyDao {
    public void increaseMoney(Integer id, Double money) {

        String sql = "update t_money set money = money + ? where id = ? ";
        super.getJdbcTemplate().update(sql,money,id);
    }

    public void decreaseMoney(Integer id, Double money) {
        String sql = "update t_money set money = money - ? where id = ? ";
        super.getJdbcTemplate().update(sql,money,id);
    }
}

(3)、書寫業務層程式碼以及其實現類

 public interface MoneyService { //轉賬方法 void transfer(Integer from,Integer to,Double money); } 

 

public class MoneyServiceImpl implements MoneyService {
    private MoneyDao moneyDao;
    private TransactionTemplate tt;

    public void setMoneyDao(MoneyDao moneyDao) {
        this.moneyDao = moneyDao;
    }

    public void setTt(TransactionTemplate tt) {
        this.tt = tt;
    }


    public void transfer(final Integer from,final Integer to,final Double money) {

        tt.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus arg0) {
                //減錢
                moneyDao.decreaseMoney(from, money);
                int i = 1/0;
                //加錢
                moneyDao.increaseMoney(to, money);
            }
        });
    }
}

(4)、配置檔案的書寫(IDEA的maven工程,配置檔案必須放在resources中)

  jdbc.properties

 jdbc.jdbcUrl=jdbc:mysql:///school jdbc.driverClass=com.mysql.jdbc.Driver jdbc.user=root jdbc.password=dyhroot 

  applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" 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-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.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-tx-4.2.xsd ">

    <!-- 指定spring讀取db.properties配置 -->
    <context:property-placeholder location="classpath:jdbc.properties"  />

    <!-- 事務核心管理器,封裝了所有事務操作. 依賴於連線池 -->
    <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" >
        <property name="dataSource" ref="dataSource" ></property>
    </bean>
    <!-- 事務模板物件 -->
    <bean name="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate" >
        <property name="transactionManager" ref="transactionManager" ></property>
    </bean>

    <!-- 配置事務通知 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager" >
        <tx:attributes>
            <!-- 以方法為單位,指定方法應用什麼事務屬性
                isolation:隔離級別
                propagation:傳播行為
                read-only:是否只讀
             -->
            <tx:method name="save*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
            <tx:method name="persist*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
            <tx:method name="update*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
            <tx:method name="modify*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
            <tx:method name="delete*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
            <tx:method name="remove*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
            <tx:method name="get*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true" />
            <tx:method name="find*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true" />
            <tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
        </tx:attributes>
    </tx:advice>

    <!-- 配置織入 -->
    <aop:config  >
        <!-- 配置切點表示式 -->
        <aop:pointcut expression="execution(* dyh.service.*ServiceImpl.*(..))" id="txPc"/>
        <!-- 配置切面 : 通知+切點
                 advice-ref:通知的名稱
                 pointcut-ref:切點的名稱
         -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPc" />
    </aop:config>
    
    <!-- 1.將連線池 -->
    <bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
        <property name="jdbcUrl" value="${jdbc.jdbcUrl}" ></property>
        <property name="driverClass" value="${jdbc.driverClass}" ></property>
        <property name="user" value="${jdbc.user}" ></property>
        <property name="password" value="${jdbc.password}" ></property>
    </bean>
    
    <!-- 2.Dao-->
    <bean name="moneyDao" class="dyh.dao.MoneyDaoImpl" >
        <property name="dataSource" ref="dataSource" ></property>
    </bean>

    <!-- 3.Service-->
    <bean name="accountService" class="dyh.service.MoneyServiceImpl" >
        <property name="moneyDao" ref="moneyDao" ></property>
        <property name="tt" ref="transactionTemplate" ></property>
    </bean>

</beans>

(5)、測試

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestDemo {
    @Resource(name="accountService")
    private MoneyService as;

    @Test
    public void fun1(){

        as.transfer(1, 2, 100d);

    }
}

 

測試結果:

出現異常:

資料庫中的資料沒有改變,如果在業務層去除

 int i = 1/0;

資料庫中結果改變




利用註解的方式實現事務的操作(根據上面程式碼,兩個地方需要改變:1、配置檔案。2、業務層程式碼):
(1)、配置檔案修改
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" 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-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.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-tx-4.2.xsd ">

    <!-- 指定spring讀取db.properties配置 -->
    <context:property-placeholder location="classpath:jdbc.properties"  />

    <!-- 事務核心管理器,封裝了所有事務操作. 依賴於連線池 -->
    <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" >
        <property name="dataSource" ref="dataSource" ></property>
    </bean>
    <!-- 事務模板物件 -->
    <bean name="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate" >
        <property name="transactionManager" ref="transactionManager" ></property>
    </bean>

    <!-- 開啟使用註解管理aop事務 -->
    <tx:annotation-driven/>

    <!-- 1.將連線池 -->
    <bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
        <property name="jdbcUrl" value="${jdbc.jdbcUrl}" ></property>
        <property name="driverClass" value="${jdbc.driverClass}" ></property>
        <property name="user" value="${jdbc.user}" ></property>
        <property name="password" value="${jdbc.password}" ></property>
    </bean>

    <!-- 2.Dao-->
    <bean name="moneyDao" class="dyh.dao.MoneyDaoImpl" >
        <property name="dataSource" ref="dataSource" ></property>
    </bean>

    <!-- 3.Service-->
    <bean name="accountService" class="dyh.service.MoneyServiceImpl" >
        <property name="moneyDao" ref="moneyDao" ></property>
        <property name="tt" ref="transactionTemplate" ></property>
    </bean>

</beans>

(2)、業務層程式碼修改(@Transaction   該註解一般放在業務層類上面,進行這個類的操作)

public class MoneyServiceImpl implements MoneyService {
    private MoneyDao moneyDao;
    private TransactionTemplate tt;

    public void setMoneyDao(MoneyDao moneyDao) {
        this.moneyDao = moneyDao;
    }

    public void setTt(TransactionTemplate tt) {
        this.tt = tt;
    }

    @Transactional
    public void transfer(Integer from, Integer to, Double money) {
//減錢
        moneyDao.decreaseMoney(from, money);
        int i = 1/0;
        //加錢
        moneyDao.increaseMoney(to, money);
    }
}