使用 Spring Data R2DBC 进行批量插入时检索生成的 ID

Posted

技术标签:

【中文标题】使用 Spring Data R2DBC 进行批量插入时检索生成的 ID【英文标题】:Retrieve generated IDs when doing bulk insert using Spring Data R2DBC 【发布时间】:2021-01-02 07:11:38 【问题描述】:

我有一个场景,我的表有一个自动生成的 id 列,我需要将项目批量插入 db 并获取生成的 id。有什么办法可以实现吗?

这是我的桌子:

CREATE TABLE test_table (
  `id` SERIAL NOT NULL,
  `name` VARCHAR(100) NOT NULL,
  `created_date` DATETIME NOT NULL,
  PRIMARY KEY (`id`)
);

要将项目列表保存到此表中,我正在使用的代码:

String initialSql = "INSERT INTO test_table(`name`,`created_date`) VALUES ";

    List<String> values =
        dummyEntities.stream()
            .map(dummyEntity -> "('" + dummyEntity.getName() + "','"
                + dummyEntity.getCreatedDate().atZoneSameInstant(ZoneId.of("UTC")).toLocalDateTime().toString() + "')")
            .collect(Collectors.toList());

    String sqlToExecute =  initialSql + String.join(",", values);
    client.execute(sqlToExecute)
             .//Then what?

生成的 SQL 语句(来自 DEBUG 日志):

2020-09-15 18:59:32.613 DEBUG 44801 --- [actor-tcp-nio-1] o.s.d.r2dbc.core.DefaultDatabaseClient   : Executing SQL statement [INSERT INTO test_table(`name`,`created_date`) VALUES ('Abhi57','2020-09-15T13:29:29.951964'),('Abhi92','2020-09-15T13:29:29.952023')]

我什至尝试过使用ConnectionFactory,仍然没有任何线索

    Mono.from(connectionFactory.create())
        .map(Connection::createBatch)
        .map(batch -> 
          dummyEntities.forEach(dummyEntity -> 
            String sql = String.format("INSERT INTO `test_table` (`name`,`created_date`) VALUES ('%s','%s');", dummyEntity.getName(),
                dummyEntity.getCreatedDate().atZoneSameInstant(ZoneId.of("UTC")).toLocalDateTime().toString());
            batch.add(sql);
          );
          return batch;
        )
        .flatMap(batch -> Mono.from(batch.execute()))
        .//Then what?

作为参考,dummyEntities 变量包含DummyEntity 对象的列表。 DummyEntity 类看起来像这样:

@Table("test_table")
public class DummyEntity implements Persistable<Long> 

  @Id
  @Column("id")
  private Long id;

  @Column("name")
  private String name;

  @Column("created_date")
  private OffsetDateTime createdDate;

  //Getter,Setter
  @Override
  public boolean isNew() 
    return id == null;
  

使用的依赖:2.3.2.RELEASE

    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    implementation 'org.springframework.boot:spring-boot-starter-data-r2dbc'
    implementation 'dev.miku:r2dbc-mysql:0.8.2.RELEASE'

【问题讨论】:

您要查找的是client.execute( ... ).all() 记录在此处docs.spring.io/spring-data/r2dbc/docs/1.1.3.RELEASE/reference/…,具体取决于您获得不同对象的查询,但我认为您正在寻找的是all() 【参考方案1】:

响应迟了,但由于我在 R2DBC PostgreSQL 上遇到了同样的问题,这里有一个可能的解决方案:

// -- generate 100 Product entities and mark them as CREATE
// -- Product is implementing Persistable<UUID> and isNew() will return TRUE for CREATE and FALSE for UPDATE 
List<Product> list = Stream.generate(() -> Product.builder()
    .articleNumber(UUID.randomUUID().toString().substring(0, 10))
    .name(UUID.randomUUID().toString())
    .images(Arrays.asList("1", "2"))
    .saveAction(SaveAction.CREATE)
    .build()).limit(100).collect(Collectors.toList());

// -- create the PostgresqlBatch and return the Result flux
Flux<? extends Result> flux = databaseClient.inConnectionMany(connection -> 
  Batch batch = connection.createBatch();

  list.forEach(p -> 
    batch.add("INSERT INTO product(\"id\",\"article_number\",\"name\") VALUES ('" + p.getId() + "','" + p.getArticleNumber() + "','" + p
        .getName() + "') RETURNING id, name, article_number");
  );

  return Flux.from(batch.execute());
);

// -- transform the Result flux into a Product flux
return flux.flatMap(result -> result.map((row, rowMetadata) -> Product.builder()
    .id(row.get("id", UUID.class))
    .name(row.get("name", String.class))
    .articleNumber(row.get("article_number", String.class))
    .build()));

secretRETURNING column1, column2, column3 添加到每个插入语句中的。

【讨论】:

【参考方案2】:

使用原来的ConnectionFacotory很容易得到生成的id。

我尝试使用ConnectionFactory 来获取生成的ID,按预期工作。

                    .thenMany(
                            Mono.from(conn)
                                    .flatMapMany(
                                            c -> c.createStatement(insertSql)
                                                    .returnGeneratedValues("id")
                                                    .execute()

                                    )
                    )
                    .flatMap(data -> Flux.from(data.map((row, rowMetadata) -> row.get("id"))))
                    .doOnNext(id -> log.info("generated id: ", id))

完整的代码示例是here。

它像这样在控制台中打印日志。

2020-09-19 10:43:30,815 INFO [main] com.example.demo.H2Tests$Sql:89 generated id: 1
2020-09-19 10:43:30,815 INFO [main] com.example.demo.H2Tests$Sql:89 generated id: 2

我认为 Spring 框架 5.3 中的新 DatabaseClient 只是连接工厂的一个薄包装,并使用 filter 来获取生成的 id。

databaseClient.sql("INSERT INTO  posts (title, content, metadata) VALUES (:title, :content, :metadata)")
.filter((statement, executeFunction) -> statement.returnGeneratedValues("id").execute())

检查the complete example codes(但此示例仅检索单个 id)。

【讨论】:

嗨@Hantsy,很抱歉这么晚才回复。我试过你的代码。但它只返回一个生成的 id(批次的第一个 id)。在这里查看:friendpaste.com/10LKeGYsUF2OJWPUjzrDh7。你能指出我在这里做错了什么吗? 不确定您的代码中的确切原因,无法从代码片段中确定。我尝试了我的代码,它按预期工作。 你使用的是同一个 Miku Mysql 驱动吗?从您分享的代码 sn-p 来看,它是针对 H2 的。 试过 MySQL,是的,它只返回最后一个 id。也许它来自声明last_insert_id 是的,非响应式驱动程序如何返回所有生成的 id?一定有什么办法..不是@Hantsy吗?

以上是关于使用 Spring Data R2DBC 进行批量插入时检索生成的 ID的主要内容,如果未能解决你的问题,请参考以下文章

Spring Data R2DBC响应式操作MySQL

Spring Data R2DBC 响应式数据库操作使用

Spring认证中国教育管理中心-Spring Data R2DBC框架教程一

Spring Data R2dbc中处理表之间关系的最佳实践

MYSQL R2DBC 的 Spring Data 多主机设置

Spring Data r2dbc - 实体继承