1. 概述
- 解決 jdbcTemplate 下, update 結果不帶 自增id 的問題
2. 場景
- 看書 Spring in Action 5th
- 3.1.4
- listing 3.10
- saveTacoInfo 方法
- 問題
- 每次插入 都是成功的
- 死活不返回自增 id
- 導致 500
- 問題
- saveTacoInfo 方法
- listing 3.10
- 3.1.4
3. 環境
-
os
- win10
-
jdk
- 1.8
-
ide
- ida 2018.1
-
spring
- spring boot
- 2.1.7 release
- 組件
- thymeleaf
- starter-web
- devtool
- starter-test
- spring boot
-
browser
- firefox
- 70.0
- firefox
-
H2
- 1.4.197
-
ref
- spring in action 5th
4. 問題的發現與處理
1. 問題發現
-
嘗試
-
正在做 3.1.4 的代碼
- saveTacoInfo 方法
-
簡單施工之后, 我開始調試
-
-
中途一堆錯
-
這個是因為自己菜
- sql 語句寫錯了 表名
-
Taco 類里的 ingredents 忽然就變成了 List
-
我從 第二章 結束的代碼開始改
- 發現書上是 Ingredient 而 代碼是 String
- 我按書上的改了 Taco 類, 改了 方法
-
結果
- 測試類又過不去了
- 有單測倒是挺不錯
- save 方法又不對了
- 測試類又過不去了
-
想了想, 這個變量用 String 表示, 還是不怎么影響邏輯
- 又都改成了 String
-
-
還有些小毛病, 就不說了
-
完事后總算沒有 500 了
-
-
design 頁面
- 按要求填寫 taco 信息, 然后提交
-
報錯
- 500
- NullPointerException
- 本來該拿回來的自增 id 沒拿回來
- NullPointerException
- 500
-
問題出現后
- 我的第一反應, 還是覺得是自己的問題
-
- 按要求填寫 taco 信息, 然后提交
-
問題代碼段
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. 問題處理
-
確認數據庫
- 發現我之前的數據, 是成功寫了 taco 表的
- 內容也沒有差錯, id 也生成了
- 發現我之前的數據, 是成功寫了 taco 表的
-
檢查代碼
- 使用 vimdiff 對關鍵代碼段做比對
- 發現沒有問題
- 使用 vimdiff 對關鍵代碼段做比對
-
斷點
- debug
- 發現確實 keyHolder 里面就是空的
- debug
-
嘗試修改返回值
-
我修改了方法的返回值
- 想看看, 是否是這個方法的問題
-
第一次: 改成了 100
- 結果
- 觸發了異常
- 提示我 觸發了 sql 的約束
- 結果
-
第二次: 改成了 1
- 結果
- 成功跳轉
- 結果
-
-
結論
- jdbcTemplate 的 update 方法, 沒有取到 返回的自增id
-
查找答案
-
百度關鍵字: jdbc template update id
- 結果跟這個例子, 居然都差不多
- 好些個都是這樣
- 這一個耽誤了我不少時間
- 我又跑回去重新檢查代碼
- 結果跟這個例子, 居然都差不多
-
百度關鍵字: jdbc template 返回 自增id
- 發現前兩個用的方法和我不一樣
- 我是用 factory 生成 creator, 然后直接把 creator 和 keyholder 傳給 update
- 別人的結果, 是 通過 conn 獲取了 preparedstatement
- 但是在 preparedstatement 的參數里, 有個標志位
- Statement.RETURN_GENERATED_KEYS
- 但是在 preparedstatement 的參數里, 有個標志位
- 發現前兩個用的方法和我不一樣
-
bing 結果
- 找到一個 拉美老哥 2013 年寫的帖子
- 發現和前面的又不一樣
- 他在 獲取 preparedstatement 時, 傳了個 String[] 參數
- 發現和前面的又不一樣
- 找到一個 拉美老哥 2013 年寫的帖子
-
-
驗證
- 嘗試了 拉美老哥 的寫法
- 通過了, 獲取到了 自增id
- 嘗試了 拉美老哥 的寫法
-
想了想
- 為啥 直接獲取 statement 的兩個人, 都傳了個標記位, 而我啥事沒做呢
- 感覺我也應該有個什么開關之類的東西
- 為啥 直接獲取 statement 的兩個人, 都傳了個標記位, 而我啥事沒做呢
-
查找資料
-
這個 標記, 之前是給 statement 的
- 所以可以找找 factory, creator 和 statement 的文檔
-
結果在 factory 的 api 頁面上, 找到了這么個方法
- setReturnGeneratedKeys
-
-
試了試
-
調用並給了 true
- 果然好使了
-
debug 看了看默認值
- 果然是 false
-
3. 最后處理
-
代碼
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. 吐槽
- 這本書讓我有點難受
-
前提
- 我覺得寫這種技術書的基本原則
- 讓大多數人能夠看懂
- 如果內容確實難, 希望你的邏輯是清晰的
- 而且盡量不要引導讀者去犯錯
- 用一步一個腳印的方法講述, 會比較好點
- 一來很快就有明確的反饋, 知道自己對錯
- 而來明確的反饋, 很容易提升讀者的信心
- 我覺得寫這種技術書的基本原則
-
這本書的槽點
-
一上來就講一大堆新東西, 讓真正的新手難以接受
-
我剛好有點 Java 基礎, 知道 mvc, 知道 spring
-
但是一上來那么多陌生的概念, 如果是新人, 多半會被砸暈
- 好些對我來說也是陌生的
- 雖然不太明白, 但是並不影響我閱讀
-
而且很多新東西, 並沒有一個太明確的交代
- 這個估計作者也是覺得一下子扯入的東西太多, 沒法兩下說清
- 那你不要扯這么寬啊
- 這個估計作者也是覺得一下子扯入的東西太多, 沒法兩下說清
-
-
講解的方式, 不太合理
-
作者喜歡一次把一個長鏈條拉通
-
假設場景是這樣
- 鏈有 節點1, 節點2, 節點3, 節點4
-
作者的講解
- 構造節點1
- 構造節點2
- 構造節點3
- 構造節點4
- 最后連起來, 看看有沒有問題
- 這是書中 第二章 的講解
-
結果
- 新手看到這么多東西, 早 tm 懵逼了
- 前面 4 步沒有反饋, 根本不知道做沒做好
- 到了第 5 步, 一看出了個錯誤, 結果根本不知道不知道問題出在哪, 是在哪個鏈條, 還是在鏈條之間的連接
- 新手看到這么多東西, 早 tm 懵逼了
-
我的思路
- 構造節點1
- 簡單驗證節點1
- 構造節點2
- 簡單驗證節點2
- 連接 節點1 和 節點2
- ...
-
-
作者甚至喜歡同時講兩根鏈條
-
假設有這么個場景
- 鏈A 有 節點A1, 節點A2, 節點A3, 節點A4
- 鏈B 有 節點B1, 節點B2, 節點B3, 節點B4
-
作者的講解
- 節點A1, 節點B1
- 節點A2, 節點B2
- 節點A3, 節點B3
- 節點A4, 節點B4
- 好, 我們把這些東西串起來
- 這是 第三章 的講解
-
結果
- 上一章, 一條鏈子都沒好, 這次一下拉兩條
-
我的思路
- 一次先把一條拉通, 再拉另一條
-
-
-
代碼: 經常引入細微改動, 但是幾乎不提, 考人眼力
-
一個類忽然就變了
- 忽然加了一個屬性
- 忽然多了一個注解
- 忽然屬性就換了個類型
-
既然都忽然了
- 你能發現就不錯了
- 別指望他給你講了
- 等你快絕望的時候, 忽然在后面又說了
-
-
代碼: 有的時候, 甚至有錯誤
- 前面三個, 我還能靠自己歸納, 翻前找后, 也許可以彌補
- 但是代碼錯這個, 我有點難受了
- 根本不能運行的代碼放到書上, 新人搞得懂才怪
-
吐槽歸吐槽, 這本書, 其實還行
- 除了 aop 之外, 講得挺全面的
- 這個可以在 spring in action 第 4 版 里找到
- 特別是 微服務相關 的內容, 能開拓很大的視野
- 除了 aop 之外, 講得挺全面的
-
-
ps
-
ref
-
其他
- 這章后面的東西大同小異, 而 jpa 我有不太感興趣
- 單元后面的內容不要太坑
-
問題
- 這次確實暴露了自己 調試能力 的不足
- 個人認為這能力很吃經驗
- 我剛畢業那會兒比現在還差...
- 這次確實暴露了自己 調試能力 的不足
