Spring - jdbcTemplate - 調試代碼: PreparedStatementCreator 生成的語句, update 之后沒有 自增id, 已解決


1. 概述

  1. 解決 jdbcTemplate 下, update 結果不帶 自增id 的問題

2. 場景

  1. 看書 Spring in Action 5th
    1. 3.1.4
      1. listing 3.10
        1. saveTacoInfo 方法
          1. 問題
            1. 每次插入 都是成功的
            2. 死活不返回自增 id
            3. 導致 500

3. 環境

  1. os

    1. win10
  2. jdk

    1. 1.8
  3. ide

    1. ida 2018.1
  4. spring

    1. spring boot
      1. 2.1.7 release
    2. 組件
      1. thymeleaf
      2. starter-web
      3. devtool
      4. starter-test
  5. browser

    1. firefox
      1. 70.0
  6. H2

    1. 1.4.197
  7. ref

    1. spring in action 5th

4. 問題的發現與處理

1. 問題發現

  1. 嘗試

    1. 正在做 3.1.4 的代碼

      1. saveTacoInfo 方法
    2. 簡單施工之后, 我開始調試

  2. 中途一堆錯

    1. 這個是因為自己菜

      1. sql 語句寫錯了 表名
    2. Taco 類里的 ingredents 忽然就變成了 List

      1. 我從 第二章 結束的代碼開始改

        1. 發現書上是 Ingredient 而 代碼是 String
        2. 我按書上的改了 Taco 類, 改了 方法
      2. 結果

        1. 測試類又過不去了
          1. 有單測倒是挺不錯
        2. save 方法又不對了
      3. 想了想, 這個變量用 String 表示, 還是不怎么影響邏輯

        1. 又都改成了 String
    3. 還有些小毛病, 就不說了

    4. 完事后總算沒有 500 了

  3. design 頁面

    1. 按要求填寫 taco 信息, 然后提交
      1. 報錯

        1. 500
          1. NullPointerException
            1. 本來該拿回來的自增 id 沒拿回來
      2. 問題出現后

        1. 我的第一反應, 還是覺得是自己的問題
  4. 問題代碼段

      private long saveTacoInfo(Taco taco) {
        taco.setCreatedAt(new Date());
        PreparedStatementCreator psc =
            new PreparedStatementCreatorFactory(
                "insert into Taco (name, createdAt) values (?, ?)",
                Types.VARCHAR, Types.TIMESTAMP
            ).newPreparedStatementCreator(
                Arrays.asList(
                    taco.getName(),
                    new Timestamp(taco.getCreatedAt().getTime())));
    
        KeyHolder keyHolder = new GeneratedKeyHolder();
        jdbc.update(psc, keyHolder);
    
        return keyHolder.getKey().longValue();
      }
    
    

2. 問題處理

  1. 確認數據庫

    1. 發現我之前的數據, 是成功寫了 taco 表的
      1. 內容也沒有差錯, id 也生成了
  2. 檢查代碼

    1. 使用 vimdiff 對關鍵代碼段做比對
      1. 發現沒有問題
  3. 斷點

    1. debug
      1. 發現確實 keyHolder 里面就是空的
  4. 嘗試修改返回值

    1. 我修改了方法的返回值

      1. 想看看, 是否是這個方法的問題
    2. 第一次: 改成了 100

      1. 結果
        1. 觸發了異常
        2. 提示我 觸發了 sql 的約束
    3. 第二次: 改成了 1

      1. 結果
        1. 成功跳轉
  5. 結論

    1. jdbcTemplate 的 update 方法, 沒有取到 返回的自增id
  6. 查找答案

    1. 百度關鍵字: jdbc template update id

      1. 結果跟這個例子, 居然都差不多
        1. 好些個都是這樣
      2. 這一個耽誤了我不少時間
        1. 我又跑回去重新檢查代碼
    2. 百度關鍵字: jdbc template 返回 自增id

      1. 發現前兩個用的方法和我不一樣
        1. 我是用 factory 生成 creator, 然后直接把 creator 和 keyholder 傳給 update
        2. 別人的結果, 是 通過 conn 獲取了 preparedstatement
          1. 但是在 preparedstatement 的參數里, 有個標志位
            1. Statement.RETURN_GENERATED_KEYS
    3. bing 結果

      1. 找到一個 拉美老哥 2013 年寫的帖子
        1. 發現和前面的又不一樣
          1. 他在 獲取 preparedstatement 時, 傳了個 String[] 參數
  7. 驗證

    1. 嘗試了 拉美老哥 的寫法
      1. 通過了, 獲取到了 自增id
  8. 想了想

    1. 為啥 直接獲取 statement 的兩個人, 都傳了個標記位, 而我啥事沒做呢
      1. 感覺我也應該有個什么開關之類的東西
  9. 查找資料

    1. 這個 標記, 之前是給 statement 的

      1. 所以可以找找 factory, creator 和 statement 的文檔
    2. 結果在 factory 的 api 頁面上, 找到了這么個方法

      1. setReturnGeneratedKeys
  10. 試了試

    1. 調用並給了 true

      1. 果然好使了
    2. debug 看了看默認值

      1. 果然是 false

3. 最后處理

  1. 代碼

    private long saveTacoInfo(Taco taco) {
        taco.setCreateAt(new Date());
    
        /* 這一段, 是 拉美老哥 的代碼
        PreparedStatementCreator psc =
                new PreparedStatementCreator() {
            @Override
            public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
                String sql = "insert into Taco (name, createdAt) values (?, ?)";
                PreparedStatement ps = connection.prepareStatement(sql, new String[]{"id"});
                ps.setString(1, taco.getName());
                ps.setTimestamp(2, new Timestamp(taco.getCreateAt().getTime()));
                return ps;
                }
        };*/
    
        // 這一段是我改的代碼 
        PreparedStatementCreatorFactory pscf = new PreparedStatementCreatorFactory(    // 創建語句
                    "insert into Taco (name, createdAt) values (?, ?)",
                    Types.VARCHAR, Types.TIMESTAMP
        );
        // 關鍵方法
        pscf.setReturnGeneratedKeys(true);
        PreparedStatementCreator psc = pscf.newPreparedStatementCreator(          // 傳參
                Arrays.asList(
                    taco.getName(),
                    new Timestamp(taco.getCreateAt().getTime())
               )
        );
    
        KeyHolder keyHolder = new GeneratedKeyHolder();
        jdbc.update(psc, keyHolder);
    
        return keyHolder.getKey().longValue();  // 獲得結果
    }
    

4. 吐槽

  1. 這本書讓我有點難受
    1. 前提

      1. 我覺得寫這種技術書的基本原則
        1. 讓大多數人能夠看懂
        2. 如果內容確實難, 希望你的邏輯是清晰的
          1. 而且盡量不要引導讀者去犯錯
          2. 用一步一個腳印的方法講述, 會比較好點
            1. 一來很快就有明確的反饋, 知道自己對錯
            2. 而來明確的反饋, 很容易提升讀者的信心
    2. 這本書的槽點

      1. 一上來就講一大堆新東西, 讓真正的新手難以接受

        1. 我剛好有點 Java 基礎, 知道 mvc, 知道 spring

        2. 但是一上來那么多陌生的概念, 如果是新人, 多半會被砸暈

          1. 好些對我來說也是陌生的
          2. 雖然不太明白, 但是並不影響我閱讀
        3. 而且很多新東西, 並沒有一個太明確的交代

          1. 這個估計作者也是覺得一下子扯入的東西太多, 沒法兩下說清
            1. 那你不要扯這么寬啊
      2. 講解的方式, 不太合理

        1. 作者喜歡一次把一個長鏈條拉通

          1. 假設場景是這樣

            1. 鏈有 節點1, 節點2, 節點3, 節點4
          2. 作者的講解

            1. 構造節點1
            2. 構造節點2
            3. 構造節點3
            4. 構造節點4
            5. 最后連起來, 看看有沒有問題
            6. 這是書中 第二章 的講解
          3. 結果

            1. 新手看到這么多東西, 早 tm 懵逼了
              1. 前面 4 步沒有反饋, 根本不知道做沒做好
              2. 到了第 5 步, 一看出了個錯誤, 結果根本不知道不知道問題出在哪, 是在哪個鏈條, 還是在鏈條之間的連接
          4. 我的思路

            1. 構造節點1
            2. 簡單驗證節點1
            3. 構造節點2
            4. 簡單驗證節點2
            5. 連接 節點1 和 節點2
            6. ...
        2. 作者甚至喜歡同時講兩根鏈條

          1. 假設有這么個場景

            1. 鏈A 有 節點A1, 節點A2, 節點A3, 節點A4
            2. 鏈B 有 節點B1, 節點B2, 節點B3, 節點B4
          2. 作者的講解

            1. 節點A1, 節點B1
            2. 節點A2, 節點B2
            3. 節點A3, 節點B3
            4. 節點A4, 節點B4
            5. 好, 我們把這些東西串起來
            6. 這是 第三章 的講解
          3. 結果

            1. 上一章, 一條鏈子都沒好, 這次一下拉兩條
          4. 我的思路

            1. 一次先把一條拉通, 再拉另一條
      3. 代碼: 經常引入細微改動, 但是幾乎不提, 考人眼力

        1. 一個類忽然就變了

          1. 忽然加了一個屬性
          2. 忽然多了一個注解
          3. 忽然屬性就換了個類型
        2. 既然都忽然了

          1. 你能發現就不錯了
          2. 別指望他給你講了
          3. 等你快絕望的時候, 忽然在后面又說了
      4. 代碼: 有的時候, 甚至有錯誤

        1. 前面三個, 我還能靠自己歸納, 翻前找后, 也許可以彌補
        2. 但是代碼錯這個, 我有點難受了
        3. 根本不能運行的代碼放到書上, 新人搞得懂才怪
      5. 吐槽歸吐槽, 這本書, 其實還行

        1. 除了 aop 之外, 講得挺全面的
          1. 這個可以在 spring in action 第 4 版 里找到
        2. 特別是 微服務相關 的內容, 能開拓很大的視野

ps

  1. ref

    1. JdbcTemplate 返回自增ID
      1. 可用結果1
    2. How to Get Auto Generated ID in Spring JDBC| Spring KeyHolder Example
      1. 拉美老哥的可用結果
    3. spring 的 api 文檔
  2. 其他

    1. 這章后面的東西大同小異, 而 jpa 我有不太感興趣
    2. 單元后面的內容不要太坑
  3. 問題

    1. 這次確實暴露了自己 調試能力 的不足
      1. 個人認為這能力很吃經驗
      2. 我剛畢業那會兒比現在還差...


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM