Flyway - 自动增量 ID 不适用于 PostgreSQL 中的测试数据
Posted
技术标签:
【中文标题】Flyway - 自动增量 ID 不适用于 PostgreSQL 中的测试数据【英文标题】:Flyway - auto increment id not working with test data in PostgreSQL 【发布时间】:2021-11-25 00:53:55 【问题描述】:在我将 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 个用户。
我正在使用 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 的用户(不起作用)
但仍然没有积极的影响。有什么想法可能是错的吗?
【问题讨论】:
【参考方案1】:你似乎对自己在做什么只了解一半......
我尝试使用所有策略@GeneratedValue
您不需要随机尝试策略,您需要选择与您当前数据库设计相匹配的策略。
改变 spring.jpa.hibernate.ddl-auto
这很危险,您应该将其设置为“none”,因为您正在使用 flyway。
在没有 id 的 init.sql 中添加用户(不起作用)
这只有在 postgresql 设置为自动生成 id 时才有效(通过序列最简单)。 从您的代码来看,情况并非如此。
可能出了什么问题?
JPA 的@GeneratedValue
能够确保在它负责创建行时生成值(这意味着当您传递EntityManager#persist
时)。它不知道也无法知道您绕过 JPA 手动插入行的 flyway 脚本。
此外,让我们看看@GeneratedValue
的strategy
属性。您选择的策略将影响 JPA 生成 ID 的方式。只有几个选项:TABLE
、SEQUENCE
、IDENTITY
和 AUTO
。由于您没有明确指定策略,因此您当前使用默认值,即AUTO
。不推荐这样做,因为它不明确,现在很难说你的代码在做什么。
在TABLE
和SEQUENCE
策略下,JPA 将与数据库进行交互以生成 ID 值。在这些情况下,JPA 负责生成值,尽管它将依赖数据库来完成。不出所料,前者将使用一个表(这很罕见,顺便说一句,但也是保证在所有 RDBMS 上工作的唯一策略),后者将使用一个序列(更常见并且几乎每个商业相关的 RDBMS 都支持)。
使用IDENTITY
,JPA 根本不会尝试生成密钥,因为此策略假定数据库将自行生成 ID 值。因此,责任完全委托给了数据库。这对于具有自己的自动增量机制的数据库来说非常有用。
Postgres 并没有真正的自动增量系统,但它有一些很好的语法糖,几乎可以让它像它一样工作:serial
“数据类型”。如果将列的数据类型指定为“serial”,它实际上将使用数据类型 int 创建,但 postgresql 也会创建一个序列并将 ID 列的默认值绑定到序列的下一个值生成器。
在您的情况下,JPA 最有可能使用 SEQUENCE 或 TABLE。由于您的 DDL 设置设置为“更新”,Hibernate 将在您背后生成一个表或序列。你应该用 pgAdmin 之类的东西检查你的数据库来验证它是什么,但我会把钱放在一个序列上(所以我假设它使用的是 SEQUENCE 策略)。
因为您没有指定@SequenceGenerator
,所以将使用默认值,AFAIK 将从 1 开始。
然后,当 JPA 尝试插入新行时,它将调用该序列以生成 ID 值。它将获取序列的下一个值,即 1。这将与您在 flyway 中手动输入的 ID 冲突。
我推荐的解决方案是:
将您的 postgresql 数据类型从 int8 重新定义为“serial”(实际上是 int + 一个序列 + 设置将 ID 列链接到序列的默认值,这样如果您没有明确指定一个 ID,postgres 将自动生成一个 ID - 小心,也不要指定null
,只是不要在插入语句中指定ID列!)
在 JPA 端将生成器策略显式设置为 IDENTITY
更新您的 flyway 脚本以插入没有明确 ID 值的用户(这将确保测试数据推进序列,因此当 JPA 稍后使用相同的序列时,它不会生成冲突的 ID)
我会说有替代解决方案,但除了使用 TABLE
策略或在内存中生成密钥(您应该避免这两件事)之外,没有真正可行的替代方案,因为它归结为使用反正是一个序列。我想可以手动指定序列,放弃 id 字段上的默认值,在插入语句中手动调用序列,并在 JPA 中显式映射序列......但我不明白你为什么要做事情对自己狠。
【讨论】:
以上是关于Flyway - 自动增量 ID 不适用于 PostgreSQL 中的测试数据的主要内容,如果未能解决你的问题,请参考以下文章
如何在单元测试中使用 Flyway 时重置 SQLite 自动增量