Spring - jdbcTemplate - 调试代码: PreparedStatementCreator 生成的语句, update 之后没有 自增id, 已解决
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 我有不太感兴趣
 - 单元后面的内容不要太坑
 
问题
- 这次确实暴露了自己 调试能力 的不足
- 个人认为这能力很吃经验
 - 我刚毕业那会儿比现在还差...
 
 
- 这次确实暴露了自己 调试能力 的不足
 
Spring - jdbcTemplate - 调试代码: PreparedStatementCreator 生成的语句, update 之后没有 自增id, 已解决的更多相关文章
- Spring JdbcTemplate的queryForList(String sql , Class<T> elementType)返回非映射实体类的解决方法
		
Spring JdbcTemplate的queryForList(String sql , Class<T> elementType)易错使用 一直用ORM,今天用JdbcTemplate ...
 - Spring 中jdbcTemplate 实现执行多条sql语句
		
说一下Spring框架中使用jdbcTemplate实现多条sql语句的执行: 很多情况下我们需要处理一件事情的时候需要对多个表执行多个sql语句,比如淘宝下单时,我们确认付款时要对自己银行账户的表里 ...
 - Spring Boot (七)MyBatis代码自动生成和辅助插件
		
一.简介 1.1 MyBatis Generator介绍 MyBatis Generator 是MyBatis 官方出品的一款,用来自动生成MyBatis的 mapper.dao.entity 的框架 ...
 - @Spring Boot程序员,我们一起给程序开个后门吧:让你在保留现场,服务不重启的情况下,执行我们的调试代码
		
前言 这篇其实是对一年前的一篇文章的补坑. @Java Web 程序员,我们一起给程序开个后门吧:让你在保留现场,服务不重启的情况下,执行我们的调试代码 当时,就是在spring mvc应用里定义一个 ...
 - webservice 服务端例子+客户端例子+CXF整合spring服务端测试+生成wsdl文件 +cxf客户端代码自动生成
		
首先到CXF官网及spring官网下载相关jar架包,这个不多说.webservice是干嘛用的也不多说. 入门例子 模拟新增一个用户,并返回新增结果,成功还是失败. 大概的目录如上,很简单. Res ...
 - Spring JdbcTemplate 查询结果集Map反向生成Java实体(转)
		
原文地址:Spring JdbcTemplate 查询结果集Map反向生成Java实体 以前写过一篇文章吐槽过Spring JdbcTemplate的queryForList方法(参见:http:// ...
 - [原创]Spring JdbcTemplate 使用总结与经验分享
		
引言 近期开发的几个项目,均是基于Spring boot框架的web后端项目,使用JdbcTemplate执行数据库操作,实际开发过程中,掌握了一些有效的开发经验,踩过一些坑,在此做个记录及总结,与各 ...
 - Spring JdbcTemplate操作小结
		
Spring 提供了JdbcTemplate 来封装数据库jdbc操作细节: 包括: 数据库连接[打开/关闭] ,异常转义 ,SQL执行 ,查询结果的转换 使用模板方式封装 jdbc数据库操作-固定流 ...
 - (转)Spring JdbcTemplate 方法详解
		
Spring JdbcTemplate方法详解 文章来源:http://blog.csdn.net/dyllove98/article/details/7772463 JdbcTemplate主要提供 ...
 
随机推荐
- 跨域 node git
			
promise 异步回调地狱:就是多个异步请求嵌套的表现 瑕疵:后期维护难 解决:通过promise技术 什么是promise:就是一种异步编程的解决方案 有三个状态:进行中.成功了,失败了 var ...
 - 使用SVN更新项目后出现冲突说明
			
使用Update后出现多个文件,并且报错!! R.java.mine R.java.r21965 R.java.r23204 下面以自动生成R.java.mine,R.java.r21965, ...
 - MySQL 8.0.18 在 Windows Server 2019 上的安装(ZIP)公开
			
AskScuti MySQL : Windows Server 2019 安装 MySQL 8.0 温馨提示:为了展现我最“魅力”的一面,请用谷歌浏览器撩我. 一切就绪,点我开撩
 - 超简单的OpenGL & WebGL & Three.js介绍_1
			
专业解释 什么是OpenGL OpenGL(Open Graphics Library即开放图形库或者“开放式图形库”)是用于渲染2D.3D矢量图形的跨语言.跨平台的应用程序编程接口(API). 这个 ...
 - 2019-08-09 纪中NOIP模拟B组
			
T1 [JZOJ1035] 粉刷匠 题目描述 windy有N条木板需要被粉刷. 每条木板被分为M个格子. 每个格子要被刷成红色或蓝色. windy每次粉刷,只能选择一条木板上一段连续的格子,然后涂上一 ...
 - Codeforces Round #601 (Div. 2)        E1	 Send Boxes to Alice (Easy Version)
			
#include <bits/stdc++.h> using namespace std; typedef long long ll; ; int a[N]; int n; bool pr ...
 - BBR在实时音视频领域的应用
			
小议BBR算法 BBR全称Bottleneck Bandwidth and RTT,它是谷歌在2016年推出的全新的网络拥塞控制算法.要说明BBR算法,就不能不提TCP拥塞算法. 传统的TCP拥塞控制 ...
 - 对json 数据的处理
			
今天一个任务是,从一个指定接口中获取 json 字符串,并将其中指定的数据,以列表的形式表示出来. 唯一注意:json 字符串,需要先转换成 json 对象. var json对象= eval('( ...
 - C语言-条件编译使用分析
			
1.基本概念 条件编译的行为类似于C语言中的if…else… 条件编译是预编译指示命令,用于控制是否编译某段代码 2.实例分析 条件编译初探 22-1.c #include <stdio ...
 - sql查询——范围查询(区间查询)
			
--范围查询(区间查询) --in() --查询年龄为18,28,38的人 select *from student where age=18 or age=28 or age=38; select ...