Spring中JDBCTemplate的事務實現

一、事務的定義及特性

(一)、事務的定義

1、事務是一個最小的不可再分割的執行單元,它由批量的DML語句構成,這些語句要麼全部執行,要不全都不執行。

2、通常一個事務對應一個完整的業務。比如典型的轉賬業務,從賬戶A中轉賬1000元給賬戶B。操作過程分爲兩步,第一步從賬戶A中扣去1000元,第二步往賬戶B中加上1000元。顯然這兩個操作要麼都得做,要麼都不做。如果只做其中一個操作,數據庫的數據就會出現問題。

3、注意:只有DML語句纔有事務,DDL和DCL沒有事務。

 

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

1、原子性(Atomicity):原子性是指事務操作爲最小單位,不可再分。要執行就全部執行,否則都不執行,不能出現執行其中一部分的情況。

2、一致性(Consistency):一致性是指一個事務操作結束後數據庫必須仍處於一致性狀態。所謂的一致性狀態,簡單理解就是數據庫的數據總體是保持不變的。

比如轉賬過程中。事務執行前賬戶A有2000元,賬戶B有1000元。那麼“賬戶A轉賬1000元給賬戶B”這個事務執行完成後,賬戶A和賬戶B的金錢總數仍要等於3000元。

3、隔離性(Isolation):隔離性是指併發執行的事務之間互不干擾,相互隔離。

4、持久性(Durability):持久性是指事務一旦被提交,它對數據庫數據的改變就是永久性的。

 

(三)、事務的操作步驟

1、確保數據庫支持事務功能

A、查看數據庫支持的引擎

mysql語句:show ENGINES;

從查詢結果我們會發現數據庫支持多種引擎,但其中能提供事務功能的只有InnoDB。

 

B、查看待操作表的引擎

mysql語句:show create table 表名;

可以看到我的account表執行引擎已經是InnoDB了

 

C、如果不是InnoDB,則將其改爲InnovationDB

mysql語句:ALTER TABLE 表名 ENGINE = INNODB;

 

2、具體步驟

A、開啓事務

start transaction;

B、定義事務的DML語句

insert into account values(50,"test");

此時我們看下account表會發現表中仍然沒有money=50,user=test這個記錄的。說明這個DML語句還沒有真正被執行。

C、提交 or 回滾

提交:commit

這時我們再查看下數據庫,就會發現新紀錄已經被添加到表中了。

回滾:rollback

(需要注意的是每次執行完commit或者rollback後,整個事務就結束了,因此這裏我們需要重新開啓一個事務)

我們看下account表,會發現DML語句確實沒有被執行

 

二、Spring中JDBC開啓事務支持

(一)、Spring的主要配置

定義一個數據源。根據自己的實際情況來配置,筆者這裏用的是DBCP的數據源,用其他數據源也是可以的。

    <!--  定義一個使用DBCP實現的數據源 -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
          destroy-method="close"
          p:driverClassName="com.mysql.jdbc.Driver"
          p:url="jdbc:mysql://localhost:3306/library?characterEncoding=UTF8"
          p:username="root"
          p:password="admin"/>
    <!-- 定義JDBC模板Bean   -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
          p:dataSource-ref="dataSource"/>

(二)、創建相應的DAO和Service類

1、AccountDAO類

package com.book.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class AccountDao {

    private JdbcTemplate jdbcTemplate;

    @Autowired
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public void reduce() {
        jdbcTemplate.update("update account set money=money-10 where user='紅紅'");
    }
    public void add(){
//        int i = 2/0;
        jdbcTemplate.update("update account set money=money+10 where user='小明'"); }
}

2、AccountService類

package com.book.service;

import com.book.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class AccountService {
    private AccountDao accountDao;

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

    public void  doAccount() {
        accountDao.reduce();
//        int i=2/0;
        accountDao.add();
    }

}

3、測試代碼

package com.book.web;

import com.book.service.LendService;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

public class Test {

    public static void main(String[] args){
        //進行類加載,並實例化對象
        BeanFactory bf = new XmlBeanFactory(new ClassPathResource("book-context.xml"));
        //獲取實例化的對象
        LendService bean = (LendService) bf.getBean("lendService");
        System.out.println("Done");
        try{
            bean.doAccount();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    
}

 

三、Spring使用事務的兩種方式

(一)、基於AOP的註解方式

1、xml文件中添加開啓事務的註解

    <!-- 第一步配置事務管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 第二步 開啓事務註解 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>

2、代碼中添加註解

在需要申明爲事務的方法前添加@Transactional(rollbackFor = { Exception.class })字段即可,Spring的事務回滾默認只處理RuntimeException,也就是運行時異常,對於一些程序員自己定義的異常並不會觸發事務的回滾。rollbackFor = {Exception.class}則指明我們要處理所有類型的異常。如下圖

3、運行測試代碼

運行前的數據庫狀態

A、沒有異常時運行結果如下:

B、手動構建異常(開啓事務的狀態)

運行結果如下:

用戶“紅紅”錢沒有減少,小明的錢也沒有增加。說明事務的回滾生效了。add()操作的異常導致reduce()操作被回滾,沒有執行成功。

C、手動構建異常(關閉事務的狀態)

運行結果如下,我們會發現賬號名爲“紅紅”的用戶money少了10,但是賬號名爲“小明”的用戶money卻沒有變

 

(二)、基於AOP的xml配置方式

1、xml中添加註解

    <!-- 第一步 配置事務管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 注入dataSource -->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 第二步 配置事務增強 -->
    <tx:advice id="txadvice" transaction-manager="transactionManager">
        <!-- 做事務操作 -->
        <tx:attributes>
            <!-- 設置進行事務操作的方法匹配規則  以do開頭的方法-->
            <tx:method name="do*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

    <!-- 第三步 配置切面 -->
    <aop:config>
        <!-- 切入點 -->
        <aop:pointcut expression="execution(* com.book.service.AccountService.*(..))" id="pointcut"/>
        <!-- 切面 -->
        <aop:advisor advice-ref="txadvice" pointcut-ref="pointcut"/>
    </aop:config>

2、運行結果如下

當出現異常時兩個用戶的money都不變,說明事務回滾成功。

 

三、事務不起作用的常見原因

(一)、確保你所操作的表支持事務機制

最好自己先在mysql中寫個簡單的事務試試。

(二)、service層處理了異常

1、原因

service層類中使用try catch 去捕獲異常後,由於該類的異常並沒有拋出,就無法觸發事務管理機制。因爲在你將doAccount()方法申明爲事務方法後,只有當doAccount()方法向上拋出異常時纔會觸發事務的回滾機制。如果你自己在doAcount方法中用try...catch處理完異常了,事務管理器就不知道出現了異常,自然不會執行回滾操作。

2、測試

如下,我們在AccountService類的doAccount()方法中增加try...catch處理機制進行測試。

我們查看下運行結果,發現事務失效了。

3、解決方案

A、將service層的try..catch提到web層中(web開發通常分爲dao、service和web三層,不理解的直接將try...catch去掉即可)。

B、使用TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()方法手動回滾(不推薦,會增加代碼之間的耦合度)

運行結果如下,說明事務起作用了。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章