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主要提供 ...
随机推荐
- 我的翻译--针对Outernet卫星信号的逆向工程
前言 Outernet[1]是一家旨在让访问国际互联网更加方便自由的公司,他们使用卫星来广播维基百科或者其他网站.目前,他们的广播主要使用三颗国际海事卫星[3]的L波段[2],使其广播覆盖全球,大多数 ...
- ubuntu18.04 编译fortran出现 ‘没有f951这个文件’处理
机器自带了gcc所以可以编译fortran文件, 使用时, gcc **.for –o ***.out 提示,没有找到f951. 然后去网上找解决方案,有的人说在其他地方找到了f951,然后把他复制到 ...
- HTML连载62-固定定位练习、z-index属性
一.固定定位应用场景 1.练习 <!DOCTYPE html> <html lang="en"> <head> <meta charset ...
- jQuery-File-Upload 使用,jQuery-File-Upload上传插件
================================ ©Copyright 蕃薯耀 2020-01-10 https://www.cnblogs.com/fanshuyao/ 一.官网地址 ...
- JDBC——DriverManager驱动管理对象
功能 1.注册驱动 注册驱动:告诉程序使用哪个驱动jar包 写代码使用:Class.forName("com.mysql.jdbc.Driver"); 查看源码 mysql-con ...
- 更新centos本地仓库(换源)
/etc/yum.repos.d/CentOS-Base.repo 1,首先进行备份 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/Cen ...
- JMeter概念
1. Test Plan 测试计划 Test Plan也就是测试计划,概念有点类似eclipse里面的project(项目.工程). 一个JMeter测试计划有很多种测试元素组成.一般至少包含一个T ...
- docker 免sudo设置(仅3个命令)
首先,下载docker, 需3话: sudo apt install docker.io sudo systemctl start docker sudo systemctl enable docke ...
- 在多租户(容器)数据库中如何创建PDB:方法4 克隆远程Non-CDB
基于版本:19c (12.2.0.3) AskScuti 创建方法:克隆远程Non-CDB(从 Non-CDB 中进行远程克隆).将 非CDB数据库PROD1 远程克隆为 CDB1 中的 PDB7 对 ...
- 番外:克隆本地PDB中其他参数和子句的说明
基于版本:19c (12.2.0.3) AskScuti 创建方法:克隆本地PDB(从本地其他PDB创建新的PDB) 对应路径:Creating a PDB --> Cloning --> ...