<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>

          腦洞打開!第一次看到這樣使用MyBatis的,看得我一愣一愣的。

          共 8977字,需瀏覽 18分鐘

           ·

          2023-08-30 08:42

          這期給大家分享一個讀者給我分享的一個關(guān)于 MyBatis 的“編程小技巧”,說真的,這騷操作,直接把我看得一愣一愣的。

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

          Demo

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

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

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

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

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

          庫存減一,訂單加一。

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

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

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

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

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

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

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

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

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

          前面說的是偽代碼。

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

          首先是 controller 接口:

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

          然后是這個 productMapper 的 selaProduct 接口:

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

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

          能用嗎?

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

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

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

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

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

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

          allowMultiQueries=true

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

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

          見名知意嘛:allow Multi Queries,允許進行多個查詢。

          最常用的場景就是用 foreach 標簽來進行批量插入或者更新的時候會用到這個配置。

          在這個參數(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ù)是干啥的,我這里就不展開描述了,我只能說這兩個玩意是一套組合拳,里面也大有文章,如果你不知道,建議你去了解一下。

          就當是課后習題了。

          我們還是先跟著主干走。

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

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

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

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

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

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

          埋雷

          千萬別這樣寫!

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

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

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

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

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

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

          超賣了,朋友。

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

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

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

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

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

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

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

          比如我前面提到的讀者舉的例子,幾個表之間有關(guān)聯(lián)關(guān)系,如果一個表的某條數(shù)據(jù)被刪除了,另外幾個表里面對應的數(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è)務執(zhí)行成功。

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

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

          你好好想想?

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

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

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

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

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

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

          看起來確實沒問題。

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

          。。。

          。。。

          。。。

          正確答案是被掛起了。

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

          SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;

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

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

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

          埋雷了。

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

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

          擴展

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

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

          還是在賣貨的場景下。

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

          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;

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

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


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

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

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

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

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

          我直接公布答案了:

          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:

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

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

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

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

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

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

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

              

            

                 

                  

          1、在一個公司死磕了 5~10 年的人最后都怎么樣了?

          2、Adobe聯(lián)創(chuàng)去世,沒他就沒有PDF,喬布斯也因他逆風翻盤

          3、新來個技術(shù)總監(jiān),禁止我們用Git的rebase???

          4、讓我來告訴你Java程序員是怎么一步一步從入行到被裁的

          5、明明是工作經(jīng)驗越久越吃香,為什么程序員卻不是?

          點分享

          點收藏

          點點贊

          點在看

          瀏覽 4962
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  日韩无码破解电影 | 蜜臀AV在线播放 | 91无码成人 | 黄色片免费观看 | a在线免费 |