如何避免 Spring Data JPA/Hibernate-generated 数据库中的 ID 冲突?

How to avoid id collisions in Spring Data JPA/Hibernate-generated database?

我们使用 dockerized postgres 数据库并让休眠自动生成 tables(使用 spring.jpa.hibernate.ddl-auto: create)用于我们的集成测试。使用像 H2 这样的东西不是一种选择,因为我们在一些地方做了一些特定于数据库的操作,例如本机 SQL 查询。

当所有实体都使用自动递增的 ID 时,有什么方法可以避免 ID 冲突?是通过偏移起始 ID,还是更好,让所有 table 都使用共享序列?

架构在 docker 容器启动时创建,table 由 Spring 数据创建 JPA/Hibernate

例子

示例使用 kotlin 语法并假定“allopen”插件应用于实体。

有时我们会遇到使用错误外键的错误,例如像这样:

@Entity
class EntityOne(
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "id", nullable = false, columnDefinition = "SERIAL")
  var id: Long,
)

@Entity
class EntityTwo(
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "id", nullable = false, columnDefinition = "SERIAL")
  var id: Long,
)

@Entity
class JoinEntity(
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "id", nullable = false, columnDefinition = "SERIAL")
  var id: Long,

  @ManyToOne
  @JoinColumn(name = "entity_one_id")
  var entityOne: EntityOne,

  @ManyToOne
  @JoinColumn(name = "entity_two_id")
  var entityTwo: EntityTwo,
)

@Repository
interface JoinEntityRepository : JpaRepository<JoinEntity, Long> {
  //
  // Bug here! Should be "WHERE entityOne.id = :entityOneId"
  //
  @Query("SELECT entityTwo FROM JoinEntity WHERE entityTwo.id = :entityOneId")
  fun findEntityTwoByEntityOneId(entityOneId: Long): Collection<EntityTwo>
}

这些错误在某些情况下很难发现,因为在创建 table 时,很可能存在与某个 Entity1 具有相同 ID 的 Entity2,因此查询成功但测试在线某处失败,因为当它返回一个或多个 Entity2 时,它不是预期的。

更糟糕的是,根据测试的范围,即使获取了错误的实体,它也可能会通过,或者仅当测试 运行 处于特定顺序时才会失败(由于 ids 变得“不同步” ”)。所以理想情况下,当传递错误的 id 时,它甚至应该找不到实体。但是因为数据库结构是从头开始创建的,而且 id 是自动递增的,所以它们总是从 1 开始。

我找到了解决方法。

在我的 resources/application.yml 中(在测试文件夹中,您很可能 不想 在主文件夹中这样做)我添加 spring.datasource.initialization-mode: always 和文件 data.sql.

data.sql内容如下:

DROP SEQUENCE IF EXISTS test_shared_sequence;
CREATE SEQUENCE test_shared_sequence;
ALTER TABLE entity_one ALTER COLUMN id SET DEFAULT nextval('test_shared_sequence');
ALTER TABLE entity_two ALTER COLUMN id SET DEFAULT nextval('test_shared_sequence');

在 Spring 自动生成 table 之后(使用 spring.jpa.hibernate.ddl-auto: create)它将 运行 此脚本中的任何内容,脚本将更改所有 tables 根据相同的序列自动生成 id,这意味着无论两个实体存储在哪个 table 中,都不会有相同的 id,因此在错误的 table 将始终失败。