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. Struts2学习-struts.xml文件配置

    学习框架过程中,一直对框架中的配置文件比较难理解,特搜集资料简要记录一下struts.xml文件遇到的问题. <?xml version="1.0" encoding=&qu ...

  2. yii2表单提交CSRF验证

    Yii2表单提交默认需要验证CSRF,如果CSRF验证不通过,则表单提交失败,解决方法如下: 第一种解决办法是关闭Csrf public $enableCsrfValidation = false; ...

  3. (转)spring 框架介绍

    转自:http://www.cnblogs.com/wawlian/archive/2012/11/17/2775435.html 1.Spring MVC简介 Spring MVC框架是有一个MVC ...

  4. react 实现圆环进度条

    import React, { useState, useEffect } from "react" import { css } from "emotion" ...

  5. Markdown上手使用

    前言 学习Markdown主要是为了更好的编辑博客(捂脸),顺便学一学Markdown语法,毕竟MarkdownPad 2放着吃灰好久了(雾) MarkdownPad2 下载 链接:https://p ...

  6. LaTeX 文字带边框

    1.使用framed宏包 \usepackage{framed} 可以使用verb|...|和verbatim环境而不使用cprotect宏包的cprotect命令 \begin{framed} \v ...

  7. PHP pdf 转 图片

    function pdf2png($pdf,$path,$page=-1) { if(!extension_loaded('imagick')) { return false; } if(!file_ ...

  8. [POI2004] PRZ - 状压dp

    很简单的子集枚举状压dp 这个 (j-1)&i 的子集枚举是真的骚气 #include <bits/stdc++.h> using namespace std; int W,n,t ...

  9. python3练习100题——006

    继续做题-经过py3测试 原题链接:http://www.runoob.com/python/python-exercise-example6.html 题目:斐波那契数列. 我的代码: def fi ...

  10. js控制日期的前或后N天,前或后一个月

    /*获取指定日期前或者后指定间隔时间* sdate:指定日期* interval:时间间隔* caret:间隔符*/function getNowFormatDate(sdate,interval,c ...