Hibernate GeneratorSequence 在 50 个条目后失败

Hibernate GeneratorSequence fails after 50 entries

我有一个具有以下 ID 配置的实体:

public class Publication implements Serializable, Identifiable {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
    @SequenceGenerator(name = "sequenceGenerator")
    private Long id;
}

使用此生成器(Liquibase 语法):

<createSequence incrementBy="10" sequenceName="sequence_generator" startValue="1" cacheSize="10"/>

和一个 Spring 数据 JPA 存储库:

@Repository
public interface PublicationRepository extends JpaRepository<Publication, Long>, JpaSpecificationExecutor<Publication> {
   // ...
}

现在我在我的应用程序中创建了大约 250 个没有 ID 的新 Publication 对象,然后执行 publicationRepository.saveAll()。我收到以下异常:

Caused by: javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [mypackage.Publication#144651]

我使用断点进行调试,发现第 50 个对象总是会发生这种情况,其中分配的 ID 突然被设置为已存在于已保存对象集中的 ID – 所以生成器似乎 return 错误的值。对于少于 50 个对象的集合,它工作正常。

同样奇怪的是:创建的对象 ID 增加了 1,而如果在我的数据库上执行 NEXT VALUE FOR sequence_generator,我将以 10 的增量获得 ID。

我是不是用错了发电机?

您需要将 SequenceGeneratorallocationSize 与序列的 incrementBy 同步。 allocationSize 的默认值为 50,这意味着在每第 50 次插入之后,hibernate 将生成 select nextval('sequence_name)`(或类似的东西,具体取决于方言),以获得 ID 的下一个起始值。

你的情况是:

  • 对于第一次插入,Hibernate 获取序列的下一个值,即 1。第一次插入 我的意思是每当 service/application 被(重新)启动时第一次插入.
  • 然后它执行 50 次插入(默认 allocationSize)而不询问 DB 序列的下一个值是什么。生成的 ID 将从 1 到 50。
  • 第 51 次插入获取序列的下一个值,即 11 (startBy + incrementBy)。之前您插入了一个 ID=11 的实体,这就是它无法插入新实体的原因(违反 PK 约束)。

此外,每次您按顺序调用 select nextval 时,它只会执行 currentValue + incrementBy。对于您的序列,它将是 1、11、21、31 等

如果启用 SQL 日志,您将看到以下内容:

  1. 第一次调用repository.save(entity)会生成
select nextval('sequence_name`);
insert into table_name(...) values (...);
  1. repository.save(entity) 保存第二个实体只会生成
insert into table_name(...) values (...);
  1. 插入 allocationSize 次后,您将再次看到:
select nextval('sequence_name`);
insert into table_name(...) values (...);

使用序列的优点是最大限度地减少了 Hibernate 需要与数据库对话以获取下一个 ID 的次数。根据您的使用情况,您可以调整 allocationSize 以获得最佳结果。

注意:评论之一建议使用allocationSize = 1,这很糟糕,会对性能产生巨大影响。对于 Hibernate,这意味着它需要在每次执行插入时发出 select nextval。换句话说,每个插入都有 2 个 SQL 语句。

注意 2:另外请记住,您还需要保持 SequenceGeneratorinitialValue 和序列的 startValue 同步. allocationSizeinitialValue 是序列生成器用来计算下一个值的两个值。

注3:值得一提的是,根据生成序列的算法(hi-lo, pooled, pooled-lo等),service/application之间可能会出现间隙重新启动。


有用的资源:

  • Hibernate pooled and pooled-lo identifier generators - 如果您希望更改序列生成器用于计算下一个值的算法。可能存在这样的情况(例如,在并发环境中),两个服务使用相同的数据库序列来生成值,并且它们生成的值可能会发生冲突。对于这种情况,一种策略优于另一种策略。