美文网首页
【SpringBoot DB系列】Jooq批量写入采坑记录

【SpringBoot DB系列】Jooq批量写入采坑记录

作者: 一灰灰blog | 来源:发表于2020-12-13 15:34 被阅读0次
    image

    【SpringBoot DB系列】Jooq批量写入采坑记录

    前面介绍了jooq的三种批量插入方式,结果最近发现这里面居然还有一个深坑,我以为的批量插入居然不是一次插入多条数据,而是一条一条的插入...,这就有点尬了

    1. 三种插入姿势

    关于项目创建以及jooq的相关使用姿势,推荐查看之前的博文: 【DB系列】Jooq之新增记录使用姿势

    下面是我们采用的三种批量插入方式

    /**
     * 通过Record执行批量添加
     *
     * 通过源码查看,这种插入方式实际上是单条单条的写入数据,和下面的一次插入多条有本质区别
     *
     * @param list
     * @return
     */
    public boolean batchSave(List<PoetBO> list) {
        List<PoetPO> poList = list.stream().map(this::bo2po).collect(Collectors.toList());
        int[] ans = dsl.batchInsert(poList).execute();
        System.out.println(JSON.toJSONString(ans));
        return true;
    }
    
    /**
     * 类sql写法,批量添加
     *
     * @param list
     * @return
     */
    public boolean batchSave2(List<PoetBO> list) {
        InsertValuesStep2<PoetPO, Integer, String> step = dsl.insertInto(table).columns(table.ID, table.NAME);
        for (PoetBO bo : list) {
            step.values(bo.getId(), bo.getName());
        }
        return step.execute() > 0;
    }
    
    /**
     * 不基于自动生成的代码,来批量添加数据
     *
     * @param list
     * @return
     */
    public boolean batchSave3(List<PoetBO> list) {
        InsertQuery insertQuery = dsl.insertQuery(DSL.table("poet"));
        for (PoetBO bo : list) {
            insertQuery.addValue(DSL.field("id", Integer.class), bo.getId());
            insertQuery.addValue(DSL.field("name", String.class), bo.getName());
            insertQuery.newRecord();
        }
    
        return insertQuery.execute() > 0;
    }
    

    请注意上面的三种批量插入方式,基本上对应的就是jooq的三种常见的用法

    • 直接借助自动生成的Record类来操作
    • 类sql的拼接写法,基本上我们平时的sql怎么写,这里就怎么用
    • InsertQuery:借助jooq提供的各种Query类来执行目标操作

    2. 日志验证

    上面三种写法中,第一种批量插入方式,并不是我们传统理解的一次插入多条记录,相反它是一条一条的插入的,我们可以通过开启jooq的日志来查看一些执行的sql情况

    配置文件 application.properties,添加下面的配置

    debug=false
    trace=false
    logging.level.org.jooq=DEBUG
    

    如果有自己的logback.xml配置文件,可以调整一下日志级别,将jooq的debug日志放出来

    一个简单的测试case

    public void test() {
      this.batchSave(Arrays.asList(new PoetBO(14, "yh"), new PoetBO(15, "yhh")));
      this.batchSave2(Arrays.asList(new PoetBO(16, "yihui"), new PoetBO(17, "yihuihui")));
      this.batchSave3(Arrays.asList(new PoetBO(18, "YiHui"), new PoetBO(19, "YiHuiBlog")));
    }
    
    image

    从上面的sql来看,后面两个确实是一次插入多条,但是第一个,也没有将具体执行的sql打印出来,所有不看源码的话,也没有办法实锤是一条一条插入的

    为了验证这个问题,一个简单的解决办法就是批量插入两条数据,第一条正常,第二条异常,如果第一条插入成功,第二条失败那就大概率是单个插入的了

    // 表结构中,name的字段最大为20,下面插入的第二条数据长度超限
    try {
        this.batchSave(Arrays.asList(new PoetBO(14, "yh"), new PoetBO(15, "1234567890098765432112345")));
    } catch (Exception e) {
        e.printStackTrace();
    }
    
    try {
        this.batchSave2(Arrays.asList(new PoetBO(16, "yihui"), new PoetBO(17, "1234567890098765432112345")));
    } catch (Exception e) {
        e.printStackTrace();
    }
    this.batchSave3(Arrays.asList(new PoetBO(18, "YiHui"), new PoetBO(19, "YiHuiBlog")));
    

    第一种批量插入失败

    image

    第二种插入失败

    image

    插入后结果

    image

    请注意上面的报错,以及最终插入的结果,第一种插入方式一个插入成功一个失败;第二种批量插入方式,两条都插入失败;

    通常情况下,一次插入多条数据时,一个插入失败,会导致整个插入都失败,如下

    image

    3. 源码分析

    上面是从日志以及结果表现来推测实际的执行情况,接下来就需要从源码角度来看一下,是否真的是单个的执行了

    省略掉具体的定位过程,直接找到org.jooq.impl.BatchCRUD#execute,对应的代码

    @Override
    public final int[] execute() throws DataAccessException {
    
        // [#1180] Run batch queries with BatchMultiple, if no bind variables
        // should be used...
        if (executeStaticStatements(configuration.settings())) {
            return executeStatic();
        }
        else {
            return executePrepared();
        }
    }
    

    上面有两种插入方式,对于插入的核心逻辑一样

    image

    遍历集合,获取单个 record,执行 CURD

    image

    II. 其他

    0. 项目

    系列博文

    项目源码

    1. 一灰灰Blog

    尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

    下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

    相关文章

      网友评论

          本文标题:【SpringBoot DB系列】Jooq批量写入采坑记录

          本文链接:https://www.haomeiwen.com/subject/moqpgktx.html