Spring深入 3.事務(wù)底層原理分析

一谅畅、數(shù)據(jù)庫的事務(wù)的基本特性

事務(wù)是區(qū)分文件存儲系統(tǒng)與Nosql數(shù)據(jù)庫重要特性之一胚迫,其存在的意義是為了保證即使在并發(fā)情況下也能正確的執(zhí)行crud操作贾铝。怎樣才算是正確的呢薯定?這時提出了事務(wù)需要保證的四個特性即ACID:

  • A: 原子性(atomicity)
    事務(wù)中各項操作始绍,要么全做要么全不做,任何一項操作的失敗都會導(dǎo)致整個事務(wù)的失敾爸丁亏推;
  • C: 一致性(consistency)
    事務(wù)結(jié)束后系統(tǒng)狀態(tài)是一致的;
  • I: 隔離性(isolation)
    并發(fā)執(zhí)行的事務(wù)彼此無法看到對方的中間狀態(tài)年堆;
  • D: 持久性(durability)
    事務(wù)完成后所做的改動都會被持久化吞杭,即使發(fā)生災(zāi)難性的失敗。
    在高并發(fā)的情況下变丧,要完全保證其ACID特性是非常困難的芽狗,除非把所有的事務(wù)串行化執(zhí)行,但帶來的負面的影響將是性能大打折扣痒蓬。很多時候我們有些業(yè)務(wù)對事務(wù)的要求是不一樣的译蒂,所以數(shù)據(jù)庫中設(shè)計了四種隔離級別,供用戶基于業(yè)務(wù)進行選擇谊却。
隔離級別 臟讀(Dirty Read) 不可重復(fù)讀(NonRepeatable Read) 幻讀(Phantom Read)
讀未提交(Read uncommitted) 可能 可能 可能
讀已提交(Read committed) 不可能 可能 可能
可重復(fù)讀(Repeatable read) 不可能 不可能 可能
可串行化(SERIALIZABLE) 不可能 不可能 不可能
臟讀 :

一個事務(wù)讀取到另一事務(wù)未提交的更新數(shù)據(jù)

不可重復(fù)讀 :

在同一事務(wù)中,多次讀取同一數(shù)據(jù)返回的結(jié)果有所不同, 換句話說, 后續(xù)讀取可以讀到另一事務(wù)已提交的更新數(shù)據(jù). 相反, “可重復(fù)讀”在同一事務(wù)中多次讀取數(shù)據(jù)時, 能夠保證所讀數(shù)據(jù)一樣, 也就是后續(xù)讀取不能讀到另一事務(wù)已提交的更新數(shù)據(jù)柔昼。

幻讀 :

查詢表中一條數(shù)據(jù)如果不存在就插入一條,并發(fā)的時候卻發(fā)現(xiàn)炎辨,里面居然有兩條相同的數(shù)據(jù)捕透。這就幻讀的問題。

二、Sring 對事務(wù)的支持與使用

知識點:
1.spring 事務(wù)相關(guān)API說明
2.聲明式事務(wù)的使用
3.事務(wù)傳播機制

  1. spring 事務(wù)相關(guān)API說明
    spring 事務(wù)是在數(shù)據(jù)庫事務(wù)的基礎(chǔ)上進行封裝擴展 其主要特性如下:
    a.支持原有的數(shù)據(jù)事務(wù)的隔離級別
    b.加入了事務(wù)傳播的概念 提供多個事務(wù)的和并或隔離的功能
    c.提供聲明式事務(wù)乙嘀,讓業(yè)務(wù)代碼與事務(wù)分離末购,事務(wù)變得更易用。

怎么樣去使用Spring事務(wù)呢虎谢?spring 提供了三個接口供使用事務(wù)盟榴。分別是:

  • TransactionDefinition
    事務(wù)定義


    image.png
  • PlatformTransactionManager
    事務(wù)管理


    image.png
  • TransactionStatus
    事務(wù)運行時狀態(tài)


    image.png
基于API實現(xiàn)事務(wù)
public class SpringTransactionExample {
    private static String url = "jdbc:mysql:///localhost:3306/test";
    private static String user = "root";
    private static String password = "123456";

    public static Connection openConnection() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");
        return conn;
    }

    public static void main(String[] args) {
        final DriverManagerDataSource ds = new DriverManagerDataSource(url, user, password);
        final TransactionTemplate template = new TransactionTemplate();
        template.setTransactionManager(new DataSourceTransactionManager(ds));
        template.execute(new TransactionCallback<Object>() {
            @Override
            public Object doInTransaction(TransactionStatus status) {
                Connection conn = DataSourceUtils.getConnection(ds);
                Object savePoint = null;
                try {
                    {
                        // 插入
                        PreparedStatement prepare = conn.
                                prepareStatement("insert INTO account (accountName,user,money) VALUES (?,?,?)");
                        prepare.setString(1, "111");
                        prepare.setString(2, "aaaa");
                        prepare.setInt(3, 10000);
                        prepare.executeUpdate();
                    }
                    // 設(shè)置保存點
                    savePoint = status.createSavepoint();
                    {
                        // 插入
                        PreparedStatement prepare = conn.
                                prepareStatement("insert INTO account (accountName,user,money) VALUES (?,?,?)");
                        prepare.setString(1, "222");
                        prepare.setString(2, "bbb");
                        prepare.setInt(3, 10000);
                        prepare.executeUpdate();
                    }
                    {
                        // 更新
                        PreparedStatement prepare = conn.
                                prepareStatement("UPDATE account SET money= money+1 where user=?");
                        prepare.setString(1, "asdflkjaf");
                        Assert.isTrue(prepare.executeUpdate() > 0, "");
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                } catch (Exception e) {
                    System.out.println("更新失敗");
                    if (savePoint != null) {
                        status.rollbackToSavepoint(savePoint);
                    } else {
                        status.setRollbackOnly();
                    }
                }
                return null;
            }
        });
    }
}

輸出

更新失敗
查詢數(shù)據(jù)庫

2、聲明示事務(wù)

我們前面是通過調(diào)用API來實現(xiàn)對事務(wù)的控制婴噩,這非常的繁瑣擎场,與直接操作JDBC事務(wù)并沒有太多的改善,所以Spring提出了聲明示事務(wù)几莽,使我們對事務(wù)的操作變得非常簡單迅办,甚至不需要關(guān)心它。

編寫服務(wù)類

@Transactional

public void addAccount(String name, int initMenoy) {

String accountid = new SimpleDateFormat("yyyyMMddhhmmss").format(new Date());

jdbcTemplate.update("insert INTO account (accountName,user,money) VALUES (?,?,?)", accountid, name, initMenoy);

// 人為報錯

int i = 1 / 0;

}

l 演示添加 @Transactional 注解和不添加注解的情況章蚣。

3站欺、事務(wù)傳播機制

類別 事務(wù)傳播類型 說明
支持當(dāng)前事務(wù) PROPAGATION_REQUIRED
(必須的)
如果當(dāng)前沒有事務(wù),就新建一個事務(wù)纤垂,如果已經(jīng)存在一個事務(wù)中矾策,加入到這個事務(wù)中。這是最常見的選擇峭沦。
支持當(dāng)前事務(wù) PROPAGATION_SUPPORTS
(支持)
支持當(dāng)前事務(wù)贾虽,如果當(dāng)前沒有事務(wù),就以非事務(wù)方式執(zhí)行熙侍。
支持當(dāng)前事務(wù) PROPAGATION_MANDATORY
(強制)
使用當(dāng)前的事務(wù)榄鉴,如果當(dāng)前沒有事務(wù),就拋出異常蛉抓。
不支持當(dāng)前事務(wù) PROPAGATION_REQUIRES_NEW
(隔離)
新建事務(wù)庆尘,如果當(dāng)前存在事務(wù),把當(dāng)前事務(wù)掛起巷送。
不支持當(dāng)前事務(wù) PROPAGATION_NOT_SUPPORTED
(不支持)
以非事務(wù)方式執(zhí)行操作驶忌,如果當(dāng)前存在事務(wù),就把當(dāng)前事務(wù)掛起笑跛。
不支持當(dāng)前事務(wù) PROPAGATION_NEVER
(強制非事務(wù))
以非事務(wù)方式執(zhí)行付魔,如果當(dāng)前存在事務(wù),則拋出異常飞蹂。
套事務(wù) PROPAGATION_NESTED
(嵌套事務(wù))
如果當(dāng)前存在事務(wù)几苍,則在嵌套事務(wù)內(nèi)執(zhí)行。如果當(dāng)前沒有事務(wù)陈哑,則執(zhí)行與PROPAGATION_REQUIRED類似的操作妻坝。

常用事務(wù)傳播機制:

  • PROPAGATION_REQUIRED伸眶,
    這個也是默認(rèn)的傳播機制;
  • PROPAGATION_NOT_SUPPORTED
    可以用于發(fā)送提示消息刽宪,站內(nèi)信厘贼、短信、郵件提示等圣拄。不屬于并且不應(yīng)當(dāng)影響主體業(yè)務(wù)邏輯嘴秸,即使發(fā)送失敗也不應(yīng)該對主體業(yè)務(wù)邏輯回滾。
  • PROPAGATION_REQUIRES_NEW
    總是新啟一個事務(wù)庇谆,這個傳播機制適用于不受父方法事務(wù)影響的操作岳掐,比如某些業(yè)務(wù)場景下需要記錄業(yè)務(wù)日志,用于異步反查族铆,那么不管主體業(yè)務(wù)邏輯是否完成岩四,日志都需要記錄下來哭尝,不能因為主體業(yè)務(wù)邏輯報錯而丟失日志哥攘;

l 演示常用事務(wù)的傳播機制

用例1:

創(chuàng)建用戶時初始化一個帳戶,表結(jié)構(gòu)和服務(wù)類如下材鹦。

表結(jié)構(gòu) 服務(wù)類 功能描述
user UserSerivce 創(chuàng)建用戶逝淹,并添加帳戶
account AccountService 添加帳戶

UserSerivce.createUser(name) 實現(xiàn)代碼

@Transactional
public void createUser(String name) {

    // 新增用戶基本信息

    jdbcTemplate.update("INSERT INTO `user` (name) VALUES(?)", name);

    //調(diào)用accountService添加帳戶

    accountService.addAccount(name, 10000);

 }

AccountService.addAccount(name,initMoney) 實現(xiàn)代碼(方法的最后有一個異常)

@Transactional(propagation = Propagation.REQUIRED)
public void addAccount(String name, int initMoney) {

    String accountid = new SimpleDateFormat("yyyyMMddhhmmss").format(new Date());

    jdbcTemplate.update("insert INTO account (accountName,user,money) VALUES (?,?,?)", accountid, name, initMenoy);

    // 出現(xiàn)分母為零的異常

    int i = 1 / 0;

}

實驗預(yù)測一:

createUser addAccount(異常) 預(yù)測結(jié)果
場景一 無事務(wù) required createUser (成功) addAccount(不成功)
場景二 required 無事務(wù) createUser (不成功) addAccount(不成功)
場景三 required not_supported createUser (不成功) addAccount(成功)
場景四 required required_new createUser (不成功) addAccount(不成功)
場景五 required(異常移至createUser方法未尾) required_new createUser(不成功)
addAccount(成功)
場景六 required(異常移至createUser方法未尾)(addAccount 方法移至createUser方法的同一個類里) required_new createUser (不成功) addAccount(不成功)

三、aop 事務(wù)底層實現(xiàn)原理

講事務(wù)原理之前我們先來做一個實驗桶唐,當(dāng)場景五的環(huán)境改變栅葡,把addAccount 方法移至UserService 類下,其它配置和代碼不變:

@Override
@Transactional
public void createUser(String name) {

    jdbcTemplate.update("INSERT INTO `user` (name) VALUES(?)", name);

    addAccount(name, 10000);

    // 人為報錯
    int i = 1 / 0;

}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addAccount(String name, int initMoney) {

    String accountid = new SimpleDateFormat("yyyyMMddhhmmss").format(new Date());

    jdbcTemplate.update("insert INTO account (accountName,user,money) VALUES (?,?,?)", accountid, name, initMoney);

}

演示新場景

經(jīng)過演示我們發(fā)現(xiàn)得出的結(jié)果與場景五并不 一至尤泽,required_new 沒有起到其對應(yīng)的作用欣簇。原因在于spring 聲明示事務(wù)使用動態(tài)代理實現(xiàn),而當(dāng)調(diào)用同一個類的方法時坯约,是會不會走代理邏輯的熊咽,自然事務(wù)的配置也會失效。

通過一個動態(tài)代理的實現(xiàn)來模擬這種場景

UserSerivce proxyUserSerivce = (UserSerivce) Proxy.newProxyInstance(LubanTransaction.class.getClassLoader(),
        new Class[]{UserSerivce.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                try {

                    System.out.println("開啟事務(wù):"+method.getName());

                    return method.invoke(userSerivce, args);

                } finally {

                    System.out.println("關(guān)閉事務(wù):"+method.getName());

                }

            }

        });

proxyUserSerivce.createUser("kpioneer");

當(dāng)我們調(diào)用createUser 方法時 僅打印了 createUser 的事務(wù)開啟闹丐、關(guān)閉横殴,并沒有打印addAccount 方法的事務(wù)開啟、關(guān)閉卿拴,由此可見addAccount 的事務(wù)配置是失效的衫仑。

如果業(yè)務(wù)當(dāng)中上真有這種場景該如何實現(xiàn)呢?
在spring xml中配置 暴露proxy 對象堕花,然后在代碼中用AopContext.currentProxy() 就可以獲當(dāng)前代理對象

<!-- 配置暴露proxy -->
<aop:aspectj-autoproxy expose-proxy="true"/>

// 基于代理對象調(diào)用創(chuàng)建帳戶文狱,事務(wù)的配置又生效了

@Transactional
public void createUser(String name) {

    // 新增用戶基本信息

    jdbcTemplate.update("INSERT INTO `user` (name) VALUES(?)", name);

    // 暴露proxy 對象 調(diào)用accountService添加帳戶
    ((UserSerivce) AopContext.currentProxy()).addAccount(name, 10000);
    // 人為報錯
    int i = 1 / 0;
 }
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addAccount(String name, int initMoney) {

    String accountid = new SimpleDateFormat("yyyyMMddhhmmss").format(new Date());

    jdbcTemplate.update("insert INTO account (accountName,user,money) VALUES (?,?,?)", accountid, name, initMoney);
}
但是并不推薦這樣寫事務(wù),還是另寫AccountService類 調(diào)用addAccount最好
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末缘挽,一起剝皮案震驚了整個濱河市瞄崇,隨后出現(xiàn)的幾起案子陷虎,更是在濱河造成了極大的恐慌,老刑警劉巖杠袱,帶你破解...
    沈念sama閱讀 222,729評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尚猿,死亡現(xiàn)場離奇詭異,居然都是意外死亡楣富,警方通過查閱死者的電腦和手機凿掂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來纹蝴,“玉大人庄萎,你說我怎么就攤上這事√涟玻” “怎么了糠涛?”我有些...
    開封第一講書人閱讀 169,461評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長兼犯。 經(jīng)常有香客問我忍捡,道長,這世上最難降的妖魔是什么切黔? 我笑而不...
    開封第一講書人閱讀 60,135評論 1 300
  • 正文 為了忘掉前任砸脊,我火速辦了婚禮,結(jié)果婚禮上纬霞,老公的妹妹穿的比我還像新娘凌埂。我一直安慰自己,他們只是感情好诗芜,可當(dāng)我...
    茶點故事閱讀 69,130評論 6 398
  • 文/花漫 我一把揭開白布瞳抓。 她就那樣靜靜地躺著,像睡著了一般伏恐。 火紅的嫁衣襯著肌膚如雪孩哑。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,736評論 1 312
  • 那天脐湾,我揣著相機與錄音臭笆,去河邊找鬼。 笑死秤掌,一個胖子當(dāng)著我的面吹牛愁铺,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播闻鉴,決...
    沈念sama閱讀 41,179評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼茵乱,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了孟岛?” 一聲冷哼從身側(cè)響起瓶竭,我...
    開封第一講書人閱讀 40,124評論 0 277
  • 序言:老撾萬榮一對情侶失蹤督勺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后斤贰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體智哀,經(jīng)...
    沈念sama閱讀 46,657評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,723評論 3 342
  • 正文 我和宋清朗相戀三年荧恍,在試婚紗的時候發(fā)現(xiàn)自己被綠了瓷叫。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,872評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡送巡,死狀恐怖摹菠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情骗爆,我是刑警寧澤次氨,帶...
    沈念sama閱讀 36,533評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站摘投,受9級特大地震影響煮寡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜谷朝,卻給世界環(huán)境...
    茶點故事閱讀 42,213評論 3 336
  • 文/蒙蒙 一洲押、第九天 我趴在偏房一處隱蔽的房頂上張望武花。 院中可真熱鬧圆凰,春花似錦、人聲如沸体箕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽累铅。三九已至跃须,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間娃兽,已是汗流浹背菇民。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留投储,地道東北人第练。 一個月前我還...
    沈念sama閱讀 49,304評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像玛荞,于是被迫代替她去往敵國和親娇掏。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,876評論 2 361

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

  • 現(xiàn)在的我只想做一個安靜的女子勋眯,因為我的才華不能配不上我的野心婴梧,我需要靜下心來下梢,好好努力~ 現(xiàn)在的我只想做一個安靜女...
    可晗閱讀 349評論 1 1
  • 從夢中醒來,是從白日做夢到實現(xiàn)夢想的第一步塞蹭。 If you want to make your dreams co...
    查令十字街捌拾肆號閱讀 141評論 1 1
  • 每一個寫作者都應(yīng)該掌握這門語言 它讓你忘掉排版與格式孽江,專注于內(nèi)容。 精致的外表有很多番电,但有趣的靈魂卻很少竟坛。——Sa...
    satura閱讀 1,303評論 0 3
  • 走在上班的路上钧舌,能感覺出上班的人越來越少担汤,心里也是越來越不淡定了。 雖然感覺年味越來越淡洼冻,但每當(dāng)春...
    風(fēng)中的玫瑰花閱讀 204評論 0 1
  • 【洞觀世界觀察員】自從簡書和區(qū)塊鏈勾搭上了以后崭歧,這個不再是純文學(xué)的凈土。而是變成了一個熱鬧的小集市撞牢,一大批淘金的人...
    洞觀世界觀察員閱讀 277評論 5 4