<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          第一次看到這樣的編程小技巧,看得我一愣一愣的。

          共 7985字,需瀏覽 16分鐘

           ·

          2023-08-28 09:16

          最近看到一個很有意思的關(guān)于 MyBatis 的“編程小技巧”,分享給大家,希望對大家有所幫助。

          以下“我”指原文作者:

          說真的,這騷操作,直接把我看得一愣一愣的。

          我更情愿叫它:坑你沒商量之埋雷大法。

          Demo

          為了讓你絲滑入戲,我還是先給你搞個 Demo。

          因為要使用到 MyBatis 嘛,所以我們先搞兩個表。

          一個表叫做 product 表,表結(jié)構(gòu)非常簡單:

          另一個表叫做 order_info 表,表結(jié)構(gòu)也非常簡單:

          看到這兩個表出現(xiàn)的時候,你就知道我的場景是啥了,肯定是賣貨嘛。

          庫存減一,訂單加一。

          大家再熟悉不過的場景了。

          分分鐘能寫出這樣的偽代碼:

          public void saleProduct(){
              //更新庫存,庫存減一
              productMapper.updateProductCount();
              //保存訂單信息
              orderInfoMapper.saveOrderInfo();
          }

          當(dāng)然了,這個偽代碼你一眼就能看出問題:減庫存和保存訂單應(yīng)該是一個事務(wù)操作,所以應(yīng)該把這兩個動作包裹在事務(wù)里面。

          于是我們的偽代碼變成了這樣:

          public void saleProduct() {
              //開啟事務(wù)
              begin;
              //更新庫存,庫存減一
              Boolean updateSuccess = productMapper.updateProductCount();
              //保存訂單信息
              orderInfoMapper.saveOrderInfo();
              if (updateSuccess) {
                  //提交事務(wù)
                  commit;
              } else {
                  //回滾事務(wù)
                  rollback
              }
          }

          當(dāng)時讀者給我舉例的時候,完全是另外一個場景,和賣貨完全沒有任何關(guān)系。

          讀者舉的例子大概是幾個表之間有關(guān)聯(lián)關(guān)系,如果一個表的某條數(shù)據(jù)被刪除了,另外幾個表里面對應(yīng)的數(shù)據(jù)也要刪除,還有一個表需要更新狀態(tài)。

          為了更好的展示這個“編程小技巧”,我才把場景簡化到了前面提到的賣貨的樣子。

          前面說的是偽代碼。

          現(xiàn)在我給你展示一下用“編程小技巧”寫出來的真實的代碼。

          首先是 controller 接口:

          @GetMapping("/sale")
          public void sale() {
              productMapper.selaProduct();
          }

          然后是這個 productMapper 的 selaProduct 接口:

          是的,你沒有看錯,這就是一個 MyBatis 的 mapper 接口,接下來就直接到了 mapper.xml 文件里面:

          這寫法,這小技巧,我都不打算問你騷不騷,我就問你見沒見過?

          能用嗎?

          歪師傅還是太年輕,見識不夠,在這之前從來沒見過在 mapper.xml 里面能這樣去寫 sql 的。

          不說見過,在我的小腦袋里面,我是壓根就沒想過這樣去寫。所以看到這個寫法的第一反應(yīng)是:這能行嗎?這不行吧?

          于是,秉承著大膽假設(shè)小心求證的態(tài)度,寫了上面的 Demo。

          項目啟動之后發(fā)起調(diào)用,控制臺直接報了錯:

          看到這個報錯的時候,我下意識的覺得就是 MyBatis 不支持這樣的寫法,直接報錯了,這也符合我之前的認(rèn)知。

          但是,在讀者的指導(dǎo)下,他提醒我在數(shù)據(jù)庫連接的配置上加上這樣的配置:

          allowMultiQueries=true

          我的 Demo 啟動的時候,確實沒有加這個配置。但是看到這個配置的一瞬間,我開始覺得有點意思了。

          因為我知道這個配置是干嘛的。

          見名知意嘛:allow Multi Queries,允許進(jìn)行多個查詢。

          最常用的場景就是用 foreach 標(biāo)簽來進(jìn)行批量插入或者更新的時候會用到這個配置。

          在這個參數(shù)的加持下,前面 mapper.xml 里面的寫的那個 sql,很有可能就能正常執(zhí)行了。

          因為加入這個配置之后,可以在一個數(shù)據(jù)庫連接中執(zhí)行多個 sql 語句,而對于 MyBatis 或者 MySQL 的驅(qū)動來說,它并不區(qū)這“多個 sql”都是 insert 語句還是 update 語句,或者是混合著都有的語句。

          我也去 MySQL 官網(wǎng)上查詢了這個配置的含義:

          https://dev.mysql.com/doc/connector-j/8.1/en/connector-j-connp-props-security.html#cj-conn-prop_allowMultiQueries

          對于這個參數(shù),官網(wǎng)上就一句話:

          Allow the use of ";" to delimit multiple queries during one statement. This option does not affect the 'addBatch()' and 'executeBatch()' methods, which rely on 'rewriteBatchStatements' instead.
          允許在一條語句中使用"; "分隔多個查詢。該選項不會影響 "addBatch() "和 "executeBatch() "方法,因為它們依賴于 "rewriteBatchStatements"。

          在介紹 allowMultiQueries 的時候,還提到了一個 rewriteBatchStatements 參數(shù)。

          關(guān)于這個參數(shù)是干啥的,我這里就不展開描述了,我只能說這兩個玩意是一套組合拳,里面也大有文章,如果你不知道,建議你去了解一下。

          就當(dāng)是課后習(xí)題了。

          我們還是先跟著主干走。

          當(dāng)我在數(shù)據(jù)庫連接上追加配置 allowMultiQueries=true 之后,重啟了服務(wù)。

          再次發(fā)起調(diào)用。

          為了表示我的震驚,我給你搞個動圖:

          庫存減一,訂單加一,方法執(zhí)行成功了。

          還真 TM 能用,你說這事搞的,實屬是開了眼了。

          這波漲知識了,屬于未曾設(shè)想過的道路。

          埋雷

          千萬別這樣寫!

          聽歪師傅一句勸,千萬別這樣寫!

          首先這樣的寫法就不符合絕大部分程序員的認(rèn)知。

          試問誰能想到最后的 mapper.xml 里面,并不只是簡簡單單的 sql,里面居然還埋在一坨業(yè)務(wù)邏輯呢?

          關(guān)鍵是這樣寫也埋雷啊。

          舉個簡單的例子,這樣的寫法,完全沒有考慮庫存是否足夠的情況:

          比如,當(dāng)前庫存沒有了,按照這樣的寫法,還是會在 order_info 表里面插入一條數(shù)據(jù)。

          超賣了,朋友。

          只有 commit,沒有考慮回滾的情況。

          而且這樣寫根本就完全不可能考慮超賣的情況,因為你拿不到扣減庫存的操作是否執(zhí)行成功,從而無法判斷是需要 commit 還是 rollback。

          什么,你問我能不能寫存儲過程來判斷?

          能,MyBatis 確實可以調(diào)用存儲過程。

          首先,存儲過程還是得在 MySQL 里面寫好,MyBatis 只是發(fā)起調(diào)用。

          其次,趕緊打消你這個越走越遠(yuǎn)的騷想法,老老實實的寫 Java 代碼來解決這個問題,它不香嗎?

          什么,你又問我如果是不需要判斷前一條 sql 是否執(zhí)行成功的場景呢?

          比如我前面提到的讀者舉的例子,幾個表之間有關(guān)聯(lián)關(guān)系,如果一個表的某條數(shù)據(jù)被刪除了,另外幾個表里面對應(yīng)的數(shù)據(jù)也要刪除,還有一個表需要更新狀態(tài)。

          大概是這樣的:

          begin;
          delete from table1 where user_id=xxx;
          delete from table2 where user_id=xxx;
          delete from table3 where user_id=xxx;
          update table4 set user_status=1 where user_id=xxx;
          commit;

          和賣貨的場景不一樣的是,在這個場景下如果每個 sql 執(zhí)行成功,則代表業(yè)務(wù)執(zhí)行成功。

          看起來,似乎沒什么問題。

          但是我問你一個問題:這一組 SQL 一定會走都 commit 嗎?

          你好好想想?

          肯定不一定嘛,保不齊執(zhí)行的過程中出什么幺蛾子。

          舉個最簡單的例子,表寫錯了:

          在這個場景下,再次發(fā)起調(diào)用:

          程序報錯說找不到這個表。

          那么請問:此時,訂單表是否應(yīng)該有數(shù)據(jù)被插入?

          出異常了,肯定不應(yīng)該有數(shù)據(jù)插入。我看了數(shù)據(jù)庫,確實也沒有新數(shù)據(jù)插入。

          看起來確實沒問題。

          那么再請問:在這種寫法的情況下,當(dāng)前這個事務(wù)是被回滾了還是被提交了?

          。。。

          。。。

          。。。

          正確答案是被掛起了。

          通過執(zhí)行下面這個 SQL,我們可以獲取到當(dāng)前事務(wù)列表:

          SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;

          通過查詢結(jié)果可以發(fā)現(xiàn),在我們程序拋出異常之后,當(dāng)前事務(wù)還在 RUNNING 狀態(tài):

          而且,這個事務(wù)在服務(wù)重啟之前,將一直在 RUNNING 狀態(tài),即被掛起了。

          但僅從程序的角度看,拋出異常,沒有數(shù)據(jù),符合預(yù)期,沒有任何毛病。

          埋雷了。

          所以,聽歪師傅一句勸,千萬別這樣寫!

          老老實實的寫大家都看得懂的 Java 代碼,不要在 mapper.xml 里面搞事情。

          擴(kuò)展

          其實我覺得吧,前面都屬于卵用不大的知識點,因為大家一般都不會這樣去寫。

          但是既然都寫到這里了,場景也有了,我也給大家擴(kuò)展一個稍微有點用的知識。

          還是在賣貨的場景下。

          訂單加一,庫存減一是這樣的。

          begin;
          INSERT INTO order_info(`buy_name`, `buy_goods`) VALUES ('歪師傅''ipad pro頂配版');
          update product set product_count=product_count-1 where id=1 and product_count>0;
          commit;

          而庫存減一,訂單加一是這樣的:

          begin;
          update product set product_count=product_count-1 where id=1 and product_count>0;
          INSERT INTO order_info(`buy_name`, `buy_goods`) VALUES ('歪師傅''ipad pro頂配版');
          commit;

          都是包裹在事務(wù)里面,為了簡化代碼,我們假設(shè)庫存非常夠用,先不考慮 rollback 的場景。

          請問是“訂單加一,庫存減一”的性能好,還是“庫存減一,訂單加一”的性能好,還是說這二者沒有什么區(qū)別?

          首先,從執(zhí)行結(jié)果上看,這二者確實是沒有什么區(qū)別的,都能保證業(yè)務(wù)場景的正確性。

          但是當(dāng)你考慮性能的時候,肯定是“訂單加一,庫存減一”的性能更好。

          如果你沒想明白的話,我給你一個簡單的提示:在業(yè)務(wù)正確的前提下,加鎖的代碼越靠近解鎖的代碼,是不是性能越好?

          如果你還沒想明白的話,我再給你一個提示:庫存減一,它會加鎖嗎?你不管它是加表鎖、間隙鎖還是記錄鎖,我就問你它加不加鎖?

          如果你還沒反應(yīng)過來的話,說明你對于 MySQL 的加鎖機(jī)制掌握的有點薄弱,可以去加固一下。

          我直接公布答案了:

          update product set product_count=product_count-1 where id=1 and product_count>0;

          因為 where 條件中是 id=1,所以鎖是加在唯一索引上的,而且表中存在該記錄,所以只會對 id=1 這行記錄加鎖。

          針對 id=1 這一個產(chǎn)品來說,如果它是一個熱點商品,我們采取“訂單加一,庫存減一”的寫法,性能會更高一點。

          因為在加鎖頻率相同的情況下,解鎖越快的,性能越高。

          上個圖你就明白了:

          調(diào)換一個 SQL 的事兒,性能就上去了,我就問你舒不舒服?

          最后,再說個不相關(guān)的:

          我在文章最開始的地方給了這樣的一個圖片:

          你不覺得別扭嗎?

          sela 是什么鬼?

          很明顯,這個地方是一個單純的拼寫錯誤,想要打出的單詞是 sale:

          請問,當(dāng)你在程序里面看到這樣的拼寫的時候,你會怎么辦?

          如果是我,我會主動把 selaProduct 修改為 saleProduct,其他什么都不會動。

          這就是我在之前的文章中提到的一個編碼規(guī)則,童子軍軍規(guī):

          修改一個拼寫錯誤的方法名、變量名,在代碼里面也是一件很重要的小事。

          這不是代碼潔癖,這是基本的職業(yè)道德。

          因為你也不想下一個接手你代碼的人,因為看到一堆叫做“succeess、createTiem、lastUpdataBy、bussinessDate、proudectName”等等這些變量名而血壓上升,氣大傷身。

          好了,本文的技術(shù)部分就到這里啦。

          下面這個環(huán)節(jié)叫做[荒腔走板],技術(shù)文章后面我偶爾會記錄、分享點生活相關(guān)的事情,和技術(shù)毫無關(guān)系。我知道看起來很突兀,但是我喜歡,因為這是一個普通博主的生活氣息。

          荒腔走板

          周五的時候看了樂隊的夏天最新一期。

          這一期最后一個上場的樂隊,安達(dá)組合,直接炸翻了。Max 同學(xué)和我看的時候,都不敢大口呼吸。第一個音出來的時候,我就是直接愣在板凳上一動不動。

          曲畢,Max 同學(xué)和我不約而同的鼓起掌來。我也算是深刻的理解了“民族的,就是世界的”這句話。

          這個表演全程都在唱,雖然全程唱的都是我聽不懂的蒙古語,但是就是覺得這節(jié)奏、旋律、樂器、表演,這很難不拿高分。

          最后在采訪結(jié)果,才知道人家的兩個主唱都沒有上場。

          別的樂隊都是上來就王炸,他們是直接讓兩王。

          牛逼,牛逼,厲害,厲害,佩服,佩服。

          預(yù)言一波:TOP 5 預(yù)定。

          哦,還有,散人樂隊,來自四川自貢的樂隊,聽他們的聊天環(huán)節(jié),口音就特別親切,讓我想起了我的自貢朋友們。他們在西湖邊上唱《西湖》的視頻我也刷到過,當(dāng)時不知道,看了節(jié)目才知道:哦,原來是他們啊,真不錯,希望我們四川的樂隊能走遠(yuǎn)一點。

          ··············  END  ··············




          歡迎學(xué)編程的朋友們加入魚皮的 編程導(dǎo)航 ,和 2 萬多名編程學(xué)習(xí)者共享知識、交流進(jìn)步,學(xué)習(xí)魚皮全程直播開發(fā)的原創(chuàng)項目、上千篇優(yōu)質(zhì)編程學(xué)習(xí)求職經(jīng)驗分享、并獲取 1 對 1 答疑指導(dǎo)服務(wù)。

          往期推薦

          我的學(xué)習(xí)小圈子

          魚皮的靶場開源了!

          我出手了!

          14 歲,3 次給我的項目貢獻(xiàn)代碼。

          這也太強(qiáng)了!

          沖進(jìn)銀行測開,扛住了!

          瀏覽 4733
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  黑人大吊操逼 | 久久精品五月天 | 欧美性爱乱伦视频 | 欧美A视频在线观看 | 国产乱伦综合导航 |