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 我有不太感兴趣
- 单元后面的内容不要太坑
-
问题
- 这次确实暴露了自己 调试能力 的不足
- 个人认为这能力很吃经验
- 我刚毕业那会儿比现在还差...
- 这次确实暴露了自己 调试能力 的不足