首页 > 解决方案 > Flyway - 自动增量 ID 不适用于 PostgreSQL 中的测试数据

问题描述

在我将 Flyway 添加到我的项目之前,我可以运行 POST 请求并成功创建新用户,ID = 1,下一个 ID = 2 等。

然后我添加了 Flyway 来创建表并通过 V1_init.sql 插入一些测试数据:

create table "user"(
  id int8 not null,
  username varchar(255),
);
insert into "user" values (1, 'user1');
insert into "user" values (2, 'user2');
insert into "user" values (3, 'user3');

表已创建。用户被插入。

尝试运行 POST 请求 -> 错误 500

org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "organisation_pkey" Key (id)=(1) already exists.

所以我的应用程序应该添加 ID=4 的新用户,但它看起来无法识别已经添加了 3 个用户。

我正在使用通用实体:

@Getter
@Setter
@MappedSuperclass
public abstract class GenericEntity<ID extends Serializable> implements Serializable {

    @Id
    @GeneratedValue
    protected ID id;
}

应用程序属性:

spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/my-app
spring.datasource.username=user
spring.datasource.password=user
spring.jpa.hibernate.ddl-auto=update
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.properties.hibernate.format_sql=true

我尝试使用所有策略@GeneratedValue,更改 spring.jpa.hibernate.ddl-auto,在没有 id 的 init.sql 中添加用户(不起作用)

但仍然没有积极的影响。有什么想法可能是错的吗?

标签: javapostgresqlspring-bootcrudflyway

解决方案


您似乎对自己在做什么只有一半的了解...

我尝试使用所有策略@GeneratedValue

您不需要随机尝试策略,您需要选择与您当前的数据库设计相匹配的策略。

改变 spring.jpa.hibernate.ddl-auto

这是危险的,您应该将其设置为“无”,因为您正在使用飞行路线。

在没有 id 的 init.sql 中添加用户(不工作)

这只有在 postgresql 设置为自动生成 ids 时才有效(通过序列最简单)。从您的代码来看,情况并非如此。

有什么问题?

JPA@GeneratedValue能够确保在它负责创建行时生成值(这意味着当您通过 时EntityManager#persist)。它不知道也不能知道您绕过 JPA 手动插入行的 flyway 脚本。

此外,让我们看看@GeneratedValue' 的strategy属性。您选择的策略将影响JPA生成 ID 的方式。只有几个选项:TABLESEQUENCE和。由于您没有明确指定策略,因此您当前使用的是默认策略,即. 不推荐这样做,因为它不明确,现在很难说你的代码在做什么。IDENTITYAUTOAUTO

TABLEandSEQUENCE策略下,JPA 将与数据库进行交互以生成 ID 值。在这些情况下,JPA 负责生成值,尽管它将依赖数据库来完成。不出所料,前者将使用一个表(这很罕见,顺便说一句,但也是保证在所有 RDBMS 上工作的唯一策略),后者将使用一个序列(更常见并且几乎每个商业相关的 RDBMS 都支持)。使用IDENTITY,JPA 根本不会尝试生成密钥,因为此策略假定数据库将自行生成 ID 值。因此,责任完全委托给了数据库。这对于具有自己的自动增量机制的数据库来说非常有用。

Postgres 并没有真正的自动增量系统,但它有一些很好的语法糖,几乎使它像它一样工作:serial“数据类型”。如果将列的数据类型指定为“serial”,它实际上将使用数据类型 int 创建,但 postgresql 也会创建一个序列并将 ID 列的默认值绑定到序列的下一个值生成器。

在您的情况下,JPA 很可能使用 SEQUENCE 或 TABLE。由于您的 DDL 设置设置为“更新”,Hibernate 将在您背后生成一个表或序列。你应该用 pgAdmin 之类的东西检查你的数据库来验证它是什么,但我会把钱放在一个序列上(所以我假设它使用的是 SEQUENCE 策略)。因为您没有指定 a @SequenceGenerator,所以将使用默认值,AFAIK 将从 1 开始。

然后,当 JPA 尝试插入新行时,它将调用该序列以生成 ID 值。它将获得序列的下一个值,即 1。这将与您在 flyway 中手动输入的 ID 冲突。

我推荐的解决方案是:

  • 将您的 postgresql 数据类型从 int8 重新定义为“serial”(实际上是 int + 一个序列 + 设置将 ID 列链接到序列的默认值,以便如果您没有明确指定一个,postgres 将自动生成一个 ID - 小心,也不要指定null,根本不要在插入语句中指定 ID 列!)
  • IDENTITY在 JPA 端显式设置生成器策略
  • 更新你的 flyway 脚本以插入没有明确 ID 值的用户(这将确保测试数据推进序列,这样当 JPA 稍后使用相同的序列时,它不会生成冲突的 ID)

我想说有替代解决方案,但除了使用TABLE策略或在内存中生成键(您应该避免这两件事)之外,没有真正可行的替代方案,因为无论如何它都会归结为使用序列。我想可以手动指定序列,放弃 id 字段上的默认值,在插入语句中手动调用序列,并在 JPA 中显式映射序列......但我不明白你为什么要做事情对自己狠。


推荐阅读