0

我有一个具有以下 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 Data JPA 存储库:

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

现在我在我的应用程序中创建了大约 250 个Publication没有 id 的新对象,然后执行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——因此生成器似乎返回了错误的值。对于少于 50 个对象的集合,它可以正常工作。

还有什么奇怪的:创建的对象 ID 增加了 1,而如果NEXT VALUE FOR sequence_generator在我的数据库上执行,我得到的 ID 以 10 为增量。

我是不是用错了发电机?

4

1 回答 1

2

您需要将SequenceGenerator'sallocationSize与序列的incrementBy. 默认值为allocationSize50,这意味着每插入 50 次后,hibernate 将生成select nextval('sequence_name)`(或类似的东西,取决于方言),以获取 ID 的下一个起始值。

您的情况是:

  • 对于第一个插入,Hibernate 获取序列的下一个值,即 1。第一次插入是指每当服务/应用程序(重新)启动时第一次插入。
  • 然后它执行 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 需要与 DB 对话以获取下一个 ID 的次数。根据您的用例,您可以调整allocationSize以获得最佳结果。

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

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

注意 3:值得一提的是,根据用于生成序列的算法(hi-lopooledpooled-lo等),服务/应用程序重新启动之间可能会出现间隙。


有用的资源:

  • Hibernate pooled 和 pooled-lo 标识符生成器- 如果您希望更改序列生成器使用的算法来计算下一个值。可能存在两种服务使用相同的数据库序列来生成值并且它们生成的值可能发生冲突的情况(例如在并发环境中)。对于这样的情况,一种策略比另一种更好。
于 2021-09-23T08:20:21.663 回答