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主要提供 ...
随机推荐
- Struts2学习-struts.xml文件配置
学习框架过程中,一直对框架中的配置文件比较难理解,特搜集资料简要记录一下struts.xml文件遇到的问题. <?xml version="1.0" encoding=&qu ...
- yii2表单提交CSRF验证
Yii2表单提交默认需要验证CSRF,如果CSRF验证不通过,则表单提交失败,解决方法如下: 第一种解决办法是关闭Csrf public $enableCsrfValidation = false; ...
- (转)spring 框架介绍
转自:http://www.cnblogs.com/wawlian/archive/2012/11/17/2775435.html 1.Spring MVC简介 Spring MVC框架是有一个MVC ...
- react 实现圆环进度条
import React, { useState, useEffect } from "react" import { css } from "emotion" ...
- Markdown上手使用
前言 学习Markdown主要是为了更好的编辑博客(捂脸),顺便学一学Markdown语法,毕竟MarkdownPad 2放着吃灰好久了(雾) MarkdownPad2 下载 链接:https://p ...
- LaTeX 文字带边框
1.使用framed宏包 \usepackage{framed} 可以使用verb|...|和verbatim环境而不使用cprotect宏包的cprotect命令 \begin{framed} \v ...
- PHP pdf 转 图片
function pdf2png($pdf,$path,$page=-1) { if(!extension_loaded('imagick')) { return false; } if(!file_ ...
- [POI2004] PRZ - 状压dp
很简单的子集枚举状压dp 这个 (j-1)&i 的子集枚举是真的骚气 #include <bits/stdc++.h> using namespace std; int W,n,t ...
- python3练习100题——006
继续做题-经过py3测试 原题链接:http://www.runoob.com/python/python-exercise-example6.html 题目:斐波那契数列. 我的代码: def fi ...
- js控制日期的前或后N天,前或后一个月
/*获取指定日期前或者后指定间隔时间* sdate:指定日期* interval:时间间隔* caret:间隔符*/function getNowFormatDate(sdate,interval,c ...