Flyway - 自动递增 ID 不适用于 PostgreSQL 中的测试数据

Flyway - auto increment id not working with test data in 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');

Table 已创建。已插入用户。

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

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

所以我的应用应该添加 ID=4 的新用户,但它似乎无法识别已添加的 3 个用户。

我正在使用 GenericEntity:

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

    @Id
    @GeneratedValue
    protected ID id;
}

application.properties:

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,在 init.sql 中添加没有 id 的用户(不起作用)

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

你好像对自己的所作所为一知半解...

I tried to use all strategies @GeneratedValue

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

changing spring.jpa.hibernate.ddl-auto

这很危险,您应该将其设置为“none”,因为您正在使用 flyway。

adding users in init.sql without id (not working)

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

what could be wrong?

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

再来看@GeneratedValuestrategy属性。您选择的策略将影响 JPA 生成 ID 的方式。只有几个选项:TABLESEQUENCEIDENTITYAUTO。由于您没有明确指定策略,因此您当前使用的是默认值,即 AUTO。不推荐这样做,因为它不明确,现在很难说出您的代码在做什么。

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

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

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

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

我推荐的解决方案是:

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

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