1. 概述

  1. 解决 jdbcTemplate 下, update 结果不带 自增id 的问题

2. 场景

  1. 看书 Spring in Action 5th

    1. 3.1.4

      1. listing 3.10

        1. saveTacoInfo 方法

          1. 问题

            1. 每次插入 都是成功的
            2. 死活不返回自增 id
            3. 导致 500

3. 环境

  1. os

    1. win10
  2. jdk

    1. 1.8
  3. ide

    1. ida 2018.1
  4. spring

    1. spring boot

      1. 2.1.7 release
    2. 组件
      1. thymeleaf
      2. starter-web
      3. devtool
      4. starter-test
  5. browser

    1. firefox

      1. 70.0
  6. H2

    1. 1.4.197
  7. ref

    1. spring in action 5th

4. 问题的发现与处理

1. 问题发现

  1. 尝试

    1. 正在做 3.1.4 的代码

      1. saveTacoInfo 方法
    2. 简单施工之后, 我开始调试

  2. 中途一堆错

    1. 这个是因为自己菜

      1. sql 语句写错了 表名
    2. Taco 类里的 ingredents 忽然就变成了 List

      1. 我从 第二章 结束的代码开始改

        1. 发现书上是 Ingredient 而 代码是 String
        2. 我按书上的改了 Taco 类, 改了 方法
      2. 结果

        1. 测试类又过不去了

          1. 有单测倒是挺不错
        2. save 方法又不对了
      3. 想了想, 这个变量用 String 表示, 还是不怎么影响逻辑

        1. 又都改成了 String
    3. 还有些小毛病, 就不说了

    4. 完事后总算没有 500 了

  3. design 页面

    1. 按要求填写 taco 信息, 然后提交

      1. 报错

        1. 500

          1. NullPointerException

            1. 本来该拿回来的自增 id 没拿回来
      2. 问题出现后

        1. 我的第一反应, 还是觉得是自己的问题
  4. 问题代码段

      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. 问题处理

  1. 确认数据库

    1. 发现我之前的数据, 是成功写了 taco 表的

      1. 内容也没有差错, id 也生成了
  2. 检查代码

    1. 使用 vimdiff 对关键代码段做比对

      1. 发现没有问题
  3. 断点

    1. debug

      1. 发现确实 keyHolder 里面就是空的
  4. 尝试修改返回值

    1. 我修改了方法的返回值

      1. 想看看, 是否是这个方法的问题
    2. 第一次: 改成了 100

      1. 结果

        1. 触发了异常
        2. 提示我 触发了 sql 的约束
    3. 第二次: 改成了 1

      1. 结果

        1. 成功跳转
  5. 结论

    1. jdbcTemplate 的 update 方法, 没有取到 返回的自增id
  6. 查找答案

    1. 百度关键字: jdbc template update id

      1. 结果跟这个例子, 居然都差不多

        1. 好些个都是这样
      2. 这一个耽误了我不少时间
        1. 我又跑回去重新检查代码
    2. 百度关键字: jdbc template 返回 自增id

      1. 发现前两个用的方法和我不一样

        1. 我是用 factory 生成 creator, 然后直接把 creator 和 keyholder 传给 update
        2. 别人的结果, 是 通过 conn 获取了 preparedstatement
          1. 但是在 preparedstatement 的参数里, 有个标志位

            1. Statement.RETURN_GENERATED_KEYS
    3. bing 结果

      1. 找到一个 拉美老哥 2013 年写的帖子

        1. 发现和前面的又不一样

          1. 他在 获取 preparedstatement 时, 传了个 String[] 参数
  7. 验证

    1. 尝试了 拉美老哥 的写法

      1. 通过了, 获取到了 自增id
  8. 想了想

    1. 为啥 直接获取 statement 的两个人, 都传了个标记位, 而我啥事没做呢

      1. 感觉我也应该有个什么开关之类的东西
  9. 查找资料

    1. 这个 标记, 之前是给 statement 的

      1. 所以可以找找 factory, creator 和 statement 的文档
    2. 结果在 factory 的 api 页面上, 找到了这么个方法

      1. setReturnGeneratedKeys
  10. 试了试

    1. 调用并给了 true

      1. 果然好使了
    2. debug 看了看默认值

      1. 果然是 false

3. 最后处理

  1. 代码

    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. 吐槽

  1. 这本书让我有点难受

    1. 前提

      1. 我觉得写这种技术书的基本原则

        1. 让大多数人能够看懂
        2. 如果内容确实难, 希望你的逻辑是清晰的
          1. 而且尽量不要引导读者去犯错
          2. 用一步一个脚印的方法讲述, 会比较好点
            1. 一来很快就有明确的反馈, 知道自己对错
            2. 而来明确的反馈, 很容易提升读者的信心
    2. 这本书的槽点

      1. 一上来就讲一大堆新东西, 让真正的新手难以接受

        1. 我刚好有点 Java 基础, 知道 mvc, 知道 spring

        2. 但是一上来那么多陌生的概念, 如果是新人, 多半会被砸晕

          1. 好些对我来说也是陌生的
          2. 虽然不太明白, 但是并不影响我阅读
        3. 而且很多新东西, 并没有一个太明确的交代

          1. 这个估计作者也是觉得一下子扯入的东西太多, 没法两下说清

            1. 那你不要扯这么宽啊
      2. 讲解的方式, 不太合理

        1. 作者喜欢一次把一个长链条拉通

          1. 假设场景是这样

            1. 链有 节点1, 节点2, 节点3, 节点4
          2. 作者的讲解

            1. 构造节点1
            2. 构造节点2
            3. 构造节点3
            4. 构造节点4
            5. 最后连起来, 看看有没有问题
            6. 这是书中 第二章 的讲解
          3. 结果

            1. 新手看到这么多东西, 早 tm 懵逼了

              1. 前面 4 步没有反馈, 根本不知道做没做好
              2. 到了第 5 步, 一看出了个错误, 结果根本不知道不知道问题出在哪, 是在哪个链条, 还是在链条之间的连接
          4. 我的思路

            1. 构造节点1
            2. 简单验证节点1
            3. 构造节点2
            4. 简单验证节点2
            5. 连接 节点1 和 节点2
            6. ...
        2. 作者甚至喜欢同时讲两根链条

          1. 假设有这么个场景

            1. 链A 有 节点A1, 节点A2, 节点A3, 节点A4
            2. 链B 有 节点B1, 节点B2, 节点B3, 节点B4
          2. 作者的讲解

            1. 节点A1, 节点B1
            2. 节点A2, 节点B2
            3. 节点A3, 节点B3
            4. 节点A4, 节点B4
            5. 好, 我们把这些东西串起来
            6. 这是 第三章 的讲解
          3. 结果

            1. 上一章, 一条链子都没好, 这次一下拉两条
          4. 我的思路

            1. 一次先把一条拉通, 再拉另一条
      3. 代码: 经常引入细微改动, 但是几乎不提, 考人眼力

        1. 一个类忽然就变了

          1. 忽然加了一个属性
          2. 忽然多了一个注解
          3. 忽然属性就换了个类型
        2. 既然都忽然了

          1. 你能发现就不错了
          2. 别指望他给你讲了
          3. 等你快绝望的时候, 忽然在后面又说了
      4. 代码: 有的时候, 甚至有错误

        1. 前面三个, 我还能靠自己归纳, 翻前找后, 也许可以弥补
        2. 但是代码错这个, 我有点难受了
        3. 根本不能运行的代码放到书上, 新人搞得懂才怪
      5. 吐槽归吐槽, 这本书, 其实还行

        1. 除了 aop 之外, 讲得挺全面的

          1. 这个可以在 spring in action 第 4 版 里找到
        2. 特别是 微服务相关 的内容, 能开拓很大的视野

ps

  1. ref

    1. JdbcTemplate 返回自增ID

      1. 可用结果1
    2. How to Get Auto Generated ID in Spring JDBC| Spring KeyHolder Example
      1. 拉美老哥的可用结果
    3. spring 的 api 文档
  2. 其他

    1. 这章后面的东西大同小异, 而 jpa 我有不太感兴趣
    2. 单元后面的内容不要太坑
  3. 问题

    1. 这次确实暴露了自己 调试能力 的不足

      1. 个人认为这能力很吃经验
      2. 我刚毕业那会儿比现在还差...

Spring - jdbcTemplate - 调试代码: PreparedStatementCreator 生成的语句, update 之后没有 自增id, 已解决的更多相关文章

  1. Spring JdbcTemplate的queryForList(String sql , Class<T> elementType)返回非映射实体类的解决方法

    Spring JdbcTemplate的queryForList(String sql , Class<T> elementType)易错使用 一直用ORM,今天用JdbcTemplate ...

  2. Spring 中jdbcTemplate 实现执行多条sql语句

    说一下Spring框架中使用jdbcTemplate实现多条sql语句的执行: 很多情况下我们需要处理一件事情的时候需要对多个表执行多个sql语句,比如淘宝下单时,我们确认付款时要对自己银行账户的表里 ...

  3. Spring Boot (七)MyBatis代码自动生成和辅助插件

    一.简介 1.1 MyBatis Generator介绍 MyBatis Generator 是MyBatis 官方出品的一款,用来自动生成MyBatis的 mapper.dao.entity 的框架 ...

  4. @Spring Boot程序员,我们一起给程序开个后门吧:让你在保留现场,服务不重启的情况下,执行我们的调试代码

    前言 这篇其实是对一年前的一篇文章的补坑. @Java Web 程序员,我们一起给程序开个后门吧:让你在保留现场,服务不重启的情况下,执行我们的调试代码 当时,就是在spring mvc应用里定义一个 ...

  5. webservice 服务端例子+客户端例子+CXF整合spring服务端测试+生成wsdl文件 +cxf客户端代码自动生成

    首先到CXF官网及spring官网下载相关jar架包,这个不多说.webservice是干嘛用的也不多说. 入门例子 模拟新增一个用户,并返回新增结果,成功还是失败. 大概的目录如上,很简单. Res ...

  6. Spring JdbcTemplate 查询结果集Map反向生成Java实体(转)

    原文地址:Spring JdbcTemplate 查询结果集Map反向生成Java实体 以前写过一篇文章吐槽过Spring JdbcTemplate的queryForList方法(参见:http:// ...

  7. [原创]Spring JdbcTemplate 使用总结与经验分享

    引言 近期开发的几个项目,均是基于Spring boot框架的web后端项目,使用JdbcTemplate执行数据库操作,实际开发过程中,掌握了一些有效的开发经验,踩过一些坑,在此做个记录及总结,与各 ...

  8. Spring JdbcTemplate操作小结

    Spring 提供了JdbcTemplate 来封装数据库jdbc操作细节: 包括: 数据库连接[打开/关闭] ,异常转义 ,SQL执行 ,查询结果的转换 使用模板方式封装 jdbc数据库操作-固定流 ...

  9. (转)Spring JdbcTemplate 方法详解

    Spring JdbcTemplate方法详解 文章来源:http://blog.csdn.net/dyllove98/article/details/7772463 JdbcTemplate主要提供 ...

随机推荐

  1. 我的翻译--针对Outernet卫星信号的逆向工程

    前言 Outernet[1]是一家旨在让访问国际互联网更加方便自由的公司,他们使用卫星来广播维基百科或者其他网站.目前,他们的广播主要使用三颗国际海事卫星[3]的L波段[2],使其广播覆盖全球,大多数 ...

  2. ubuntu18.04 编译fortran出现 ‘没有f951这个文件’处理

    机器自带了gcc所以可以编译fortran文件, 使用时, gcc **.for –o ***.out 提示,没有找到f951. 然后去网上找解决方案,有的人说在其他地方找到了f951,然后把他复制到 ...

  3. HTML连载62-固定定位练习、z-index属性

    一.固定定位应用场景 1.练习 <!DOCTYPE html> <html lang="en"> <head> <meta charset ...

  4. jQuery-File-Upload 使用,jQuery-File-Upload上传插件

    ================================ ©Copyright 蕃薯耀 2020-01-10 https://www.cnblogs.com/fanshuyao/ 一.官网地址 ...

  5. JDBC——DriverManager驱动管理对象

    功能 1.注册驱动 注册驱动:告诉程序使用哪个驱动jar包 写代码使用:Class.forName("com.mysql.jdbc.Driver"); 查看源码 mysql-con ...

  6. 更新centos本地仓库(换源)

    /etc/yum.repos.d/CentOS-Base.repo 1,首先进行备份 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/Cen ...

  7. JMeter概念

    1. Test Plan  测试计划 Test Plan也就是测试计划,概念有点类似eclipse里面的project(项目.工程). 一个JMeter测试计划有很多种测试元素组成.一般至少包含一个T ...

  8. docker 免sudo设置(仅3个命令)

    首先,下载docker, 需3话: sudo apt install docker.io sudo systemctl start docker sudo systemctl enable docke ...

  9. 在多租户(容器)数据库中如何创建PDB:方法4 克隆远程Non-CDB

    基于版本:19c (12.2.0.3) AskScuti 创建方法:克隆远程Non-CDB(从 Non-CDB 中进行远程克隆).将 非CDB数据库PROD1 远程克隆为 CDB1 中的 PDB7 对 ...

  10. 番外:克隆本地PDB中其他参数和子句的说明

    基于版本:19c (12.2.0.3) AskScuti 创建方法:克隆本地PDB(从本地其他PDB创建新的PDB) 对应路径:Creating a PDB --> Cloning --> ...