zoukankan      html  css  js  c++  java
  • Spring

    1. 概述

    1. 解决 jdbcTemplate 下, update 结果不带 自增id 的问题
    2. 类型
      1. 这是一篇记录
        1. 不会有明确的结论
        2. 旨在记录我解决问题的过程和思路

    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 + spring mvc + mybatis + react + reflux + webpack Web
    陈忠实和路遥:日他妈的文学和你懂个锤子
    Spring+SpringMVC+MyBatis+easyUI整合基础篇
    JAVA方法中的参数用final来修饰的效果
    全球晶圆代工厂哪家强?2016年Top30名单
    EXT combobox 二级连动 清空store缓存数据
    潘通
    MySQL性能优化
    启用了不安全的HTTP方法
    Hibernate一级缓存(基于查询分析)
  • 原文地址:https://www.cnblogs.com/xy14/p/11761969.html
Copyright © 2011-2022 走看看