Spring 事務管理

什么是事務?

數(shù)據(jù)庫事務(簡稱:事務)是數(shù)據(jù)庫管理系統(tǒng)執(zhí)行過程中的一個邏輯單位束倍,由一個有限的數(shù)據(jù)庫操作序列構成。

事務應該具有4個屬性:原子性盟戏、一致性绪妹、隔離性、持久性柿究,這四個屬性通常稱為ACID特性邮旷。

  • 原子性(Atomicity):事務作為一個整體被執(zhí)行,包含在其中的對數(shù)據(jù)庫的操作要么全部被執(zhí)行蝇摸,要么都不執(zhí)行婶肩。
  • 一致性(Consistency):事務應確保數(shù)據(jù)庫的狀態(tài)從一個一致狀態(tài)轉變?yōu)榱硪粋€一致狀態(tài)办陷。一致狀態(tài)的含義是數(shù)據(jù)庫中的數(shù)據(jù)應滿足完整性約束。
  • 隔離性(Isolation):多個事務并發(fā)執(zhí)行時律歼,一個事務的執(zhí)行不應影響其他事務的執(zhí)行民镜。
  • 持久性(Durability):已被提交的事務對數(shù)據(jù)庫的修改應該永久保存在數(shù)據(jù)庫中。

事務的隔離級別

在數(shù)據(jù)庫操作中险毁,為了有效保證并發(fā)讀取數(shù)據(jù)的正確性制圈,提出的事務隔離級別。我們的數(shù)據(jù)庫鎖畔况,也是為了構建這些隔離級別存在的鲸鹦。

隔離級別 臟讀(Dirty Read) 不可重復讀(NonRepeatable Read) 幻讀(Phantom Read)
未提交讀(Read uncommitted) 可能 可能 可能
已提交讀(Read committed) 不可能 可能 可能
可重復讀(Repeatable read) 不可能 不可能 可能
可串行化(Serializable ) 不可能 不可能 不可能

說明:

  • 未提交讀(Read Uncommitted):允許臟讀,也就是可能讀取到其他會話中未提交事務修改的數(shù)據(jù)
  • 提交讀(Read Committed):只能讀取到已經(jīng)提交的數(shù)據(jù)问窃。Oracle等多數(shù)數(shù)據(jù)庫默認都是該級別 (不重復讀)
  • 可重復讀(Repeated Read):可重復讀亥鬓。在同一個事務內(nèi)的查詢都是事務開始時刻一致的,InnoDB默認級別域庇。在SQL標準中嵌戈,該隔離級別消除了不可重復讀,但是還存在幻象讀
  • 串行讀(Serializable):完全串行化的讀听皿,每次讀都需要獲得表級共享鎖熟呛,讀寫相互都會阻塞

Read Uncommitted這種級別,數(shù)據(jù)庫一般都不會用尉姨,而且任何操作都不會加鎖庵朝,這里就不討論了。

Java中的事務管理

一又厉、編程式事務

在 Spring 出現(xiàn)以前九府,編程式事務管理對開發(fā)者來說是唯一選擇。熟悉Java JDBC 的人都知道覆致,我們需要在代碼中顯式調用Connection 的setAutoCommit()侄旬、commit()、rollback()等事務管理相關的方法煌妈,這就是編程式事務管理儡羔。

1、JDBC 的編程式事務管理

public void save(User user) throws SQLException{
    Connection conn = datasource.getConnection();
    conn.setAutoCommit(false);
    try {
        PreparedStatement ps = conn.prepareStatement("insert into user(name,age) value(?,?)");
        ps.setString(1,user.getName());
        ps.setInt(2,user.getAge());
        ps.execute();
        conn.commit();
    } catch (Exception e) {
        e.printStackTrace();
        conn.rollback();
    }finally{
        conn.close();
    }
}

2璧诵、Hibernate 的編程式事務管理

Hibernate 中需要顯式調用beginTransaction()汰蜘、commit()、rollback()等事務管理相關的方法之宿,如下:

public void save(User user){
    Session session = hibernateDao.openSession();
    Transaction tx = null;
    try {
        tx = session.beginTransaction();  
        session.save(user);  
        tx.commit();  
    } catch (Exception e) {
        if(tx!=null){
            tx.rollback();
        }
    }finally{
        session.close();
    }
}

3族操、MyBatis 的編程式事務管理

MyBatis中,我們可以通過org.apache.ibatis.session.SqlSession 的commit()比被、rollback() 等事務管理相關的方法坪创,代碼如下:

    String resource = "org/mybatis/example/mybatis-config.xml";
    InputStream in = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
    
    SqlSession session = sqlSessionFactory.openSession(false); // 打開會話炕婶,事務開始  
    try {  
        IUserMapper mapper = session.getMapper(IUserMapper.class);  
        User user = new User(9, "Test transaction");  
        int affectedCount = mapper.updateUser(user); // 因后面的異常而未執(zhí)行commit語句  
        User user = new User(10, "Test transaction continuously");  
        int affectedCount2 = mapper.updateUser(user2); // 因后面的異常而未執(zhí)行commit語句  
        int i = 2 / 0; // 觸發(fā)運行時異常  
        session.commit(); // 提交會話,即事務提交  
    } catch (Exception e) {
        session.rollback(); //回滾
    } finally {  
        session.close(); // 關閉會話莱预,釋放資源  
    }  

三、Spring 事務管理

1项滑、Spring 的編程式事務管理

基于底層 API 的編程式事務管理

根據(jù)PlatformTransactionManager依沮、TransactionDefinition 和 TransactionStatus 三個核心接口,我們完全可以通過編程的方式來進行事務管理枪狂。代碼如下:

@Service
public class BankServiceImpl implements BankService {
    private BankDao bankDao;
    private TransactionDefinition txDefinition;
    private PlatformTransactionManager txManager;
    ......
    
    public boolean transfer(Long fromId, Long toId, double amount) {
        TransactionStatus txStatus = txManager.getTransaction(txDefinition);
        boolean result = false;
        try {
            result = bankDao.transfer(fromId, toId, amount);
            txManager.commit(txStatus);
        } catch (Exception e) {
            result = false;
            txManager.rollback(txStatus);
            System.out.println("Transfer Error!");
        }
        return result;
    }
}

基于 TransactionTemplate 的編程式事務管理

2危喉、Spring 的聲明式事務管理

maven依賴:

   <properties>
        <spring.version>4.3.6.RELEASE</spring.version>
        <junit.version>4.12</junit.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
            <exclusions>
                <exclusion>
                    <artifactId>commons-logging</artifactId>
                    <groupId>commons-logging</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.26</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.21</version>
        </dependency>
    </dependencies>

spring-dao.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:tx="http://www.springframework.org/schema/tx"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
            http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">

    <bean id="parentDataSource" class="com.alibaba.druid.pool.DruidDataSource"
           destroy-method="close"  abstract="true" init-method="init" >
        <!-- 初始化連接大小 -->
        <property name="initialSize" value="2" />
        <!-- 連接池最大使用連接數(shù)量 -->
        <property name="maxActive" value="10" />
        <!-- 連接池最小空閑 -->
        <property name="minIdle" value="5" />
        <!-- 獲取連接最大等待時間 -->
        <property name="maxWait" value="30000" />
        <!-- <property name="poolPreparedStatements" value="true" /> -->
        <!-- <property name="maxPoolPreparedStatementPerConnectionSize" value="33" /> -->
        <property name="validationQuery" value="SELECT 1" />
        <property name="testOnBorrow" value="false" />
        <property name="testOnReturn" value="false" />
        <property name="testWhileIdle" value="true" />
        <!-- 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接州疾,單位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="60000" />
        <!-- 配置一個連接在池中最小生存的時間辜限,單位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="25200000" />
        <!-- 打開removeAbandoned功能 -->
        <property name="removeAbandoned" value="true" />
        <!-- 1800秒,也就是30分鐘 -->
        <property name="removeAbandonedTimeout" value="1800" />
        <!-- 關閉abanded連接時輸出錯誤日志 -->
        <property name="logAbandoned" value="true" />
        <!-- 監(jiān)控數(shù)據(jù)庫 -->
        <!-- <property name="filters" value="stat" /> -->
        <property name="filters" value="mergeStat" />

    </bean>

    <!-- 配置數(shù)據(jù)源-->
    <bean id="masterDataSource" parent="parentDataSource">
        <property name="url" value="#{jdbc['master.jdbc.url']}" />
        <property name="username" value="#{jdbc['master.jdbc.username']}" />
        <property name="password" value="#{jdbc['master.jdbc.password']}" />
        <property name="driverClassName" value="#{jdbc['master.jdbc.driver']}" />
        <property name="maxActive" value="15" />
    </bean>

    <!--Spring JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="masterDataSource"/>
    </bean>


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

    <!-- 啟用事物注解 -->
    <tx:annotation-driven transaction-manager="transactionManager" />

</beans>

以用戶下單為例严蓖,譬如下單分為兩個步驟:更新商品庫存和創(chuàng)建訂單服務兩步薄嫡,代碼如下:

/**
 * ${DESCRIPTION}
 *
 * @author Ricky Fung
 * @create 2017-03-03 10:27
 */
@Service("orderBookingService")
public class OrderBookingServiceImpl implements OrderBookingService {

    @Autowired
    private GoodsItemDao goodsItemDao;

    @Autowired
    private OrderDao orderDao;

    @Transactional(rollbackFor = {BookFailureException.class})
    @Override
    public boolean book(User user, Order order) throws BookFailureException {

        boolean success = true;
        int update;
        for (OrderItem item : order.getItems()){
            update = goodsItemDao.updateStock(item.getId(), item.getQuantity());
            if(update<1){
                success = false;
                break;
            }
        }
        if(success){    //更新庫存成功
            update = orderDao.insert(order);
            if(update>0){
                return true;
            }
        }
        throw new BookFailureException("book failure");
    }
}

參考資料

分布式事務系列(1.2)Spring的事務體系:https://yq.aliyun.com/articles/39046?spm=5176.100239.blogcont39044.27.mmXvhw

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市颗胡,隨后出現(xiàn)的幾起案子毫深,更是在濱河造成了極大的恐慌,老刑警劉巖毒姨,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哑蔫,死亡現(xiàn)場離奇詭異,居然都是意外死亡弧呐,警方通過查閱死者的電腦和手機闸迷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來俘枫,“玉大人腥沽,你說我怎么就攤上這事”懒ǎ” “怎么了巡球?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長邓嘹。 經(jīng)常有香客問我酣栈,道長,這世上最難降的妖魔是什么汹押? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任矿筝,我火速辦了婚禮,結果婚禮上棚贾,老公的妹妹穿的比我還像新娘窖维。我一直安慰自己榆综,他們只是感情好,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布铸史。 她就那樣靜靜地躺著鼻疮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪琳轿。 梳的紋絲不亂的頭發(fā)上判沟,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天,我揣著相機與錄音崭篡,去河邊找鬼挪哄。 笑死,一個胖子當著我的面吹牛琉闪,可吹牛的內(nèi)容都是我干的迹炼。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼颠毙,長吁一口氣:“原來是場噩夢啊……” “哼斯入!你這毒婦竟也來了?” 一聲冷哼從身側響起吟秩,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤咱扣,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后涵防,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體闹伪,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年壮池,在試婚紗的時候發(fā)現(xiàn)自己被綠了偏瓤。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡椰憋,死狀恐怖厅克,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情橙依,我是刑警寧澤证舟,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站窗骑,受9級特大地震影響女责,放射性物質發(fā)生泄漏。R本人自食惡果不足惜创译,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一抵知、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦刷喜、人聲如沸残制。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽初茶。三九已至,卻和暖如春浊闪,著一層夾襖步出監(jiān)牢的瞬間纺蛆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工规揪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人温峭。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓猛铅,卻偏偏與公主長得像,于是被迫代替她去往敵國和親凤藏。 傳聞我的和親對象是個殘疾皇子奸忽,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn)揖庄,斷路器栗菜,智...
    卡卡羅2017閱讀 134,654評論 18 139
  • 對大多數(shù)Java開發(fā)者來說,Spring事務管理是Spring應用中最常用的功能蹄梢,使用也比較簡單疙筹。本文主要從三個方...
    sherlockyb閱讀 3,208評論 0 18
  • 1 什么是事務 生活中關于事務有一個常見的場景,即銀行用戶轉賬禁炒。簡單的講而咆,轉賬可以分為下面 2 個步驟: 查看用戶...
    millions_chan閱讀 611評論 0 4
  • 事務是一組操作的原子執(zhí)行,其中任何一筆操作失敗將導致全部操作撤銷幕袱。 什么是事務? 如上所言暴备,事務遵循ACID原則,...
    點融黑幫閱讀 7,805評論 3 36
  • spring支持編程式事務管理和聲明式事務管理兩種方式们豌。 編程式事務管理使用TransactionTemplate...
    熊熊要更努力閱讀 247評論 0 0