【问题标题】:How to avoid id collisions in Spring Data JPA/Hibernate-generated database?如何避免 Spring Data JPA/Hibernate 生成的数据库中的 id 冲突?
【发布时间】:2021-10-24 21:29:57
【问题描述】:

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

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

Schema 是在 docker 容器启动时创建的,表是由 Spring Data 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>
}

在某些情况下很难找到这些错误,因为在创建表时,很可能有一个 Entity2 与某个 Entity1 具有相同的 id,因此查询成功但测试失败了,因为虽然它返回一个或多个 Entity2,但它不是预期的。

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

【问题讨论】:

  • 错误是不是生成的 id 都以 1 开头。有很多,也许是大多数(?)系统有 100 多个表,id 就是这样生成的。该错误存在于您用于获取 id 的代码中。使用单个序列不会解决该问题,并且会降低性能,除非您使用较大的 CACHE 值。
  • 我只是在谈论在测试中使用单个序列,以便更容易地发现错误。生产数据库仍然会以这种方式做事。这不是万无一失的解决方案,但它可以解决某些错误仅在测试按一定顺序运行时才会出现的问题,从而减少运行测试所花费的时间。

标签: postgresql hibernate spring-data-jpa spring-data


【解决方案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 自动生成表格后(使用 spring.jpa.hibernate.ddl-auto: create),它将运行此脚本中的任何内容,并且该脚本将更改所有表格以根据相同的顺序自动生成 id,这意味着没有两个实体会无论它们存储在哪个表中,都具有相同的 id,因此在错误的表中查找 id 的任何查询都会失败。

【讨论】:

    猜你喜欢
    • 2015-09-12
    • 1970-01-01
    • 2020-05-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-15
    • 1970-01-01
    相关资源
    最近更新 更多