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。
我是不是用错了发电机?
您需要将 SequenceGenerator
的 allocationSize
与序列的 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 日志,您将看到以下内容:
- 第一次调用
repository.save(entity)
会生成
select nextval('sequence_name`);
insert into table_name(...) values (...);
- 用
repository.save(entity)
保存第二个实体只会生成
insert into table_name(...) values (...);
- 插入
allocationSize
次后,您将再次看到:
select nextval('sequence_name`);
insert into table_name(...) values (...);
使用序列的优点是最大限度地减少了 Hibernate 需要与数据库对话以获取下一个 ID 的次数。根据您的使用情况,您可以调整 allocationSize
以获得最佳结果。
注意:评论之一建议使用allocationSize = 1
,这很糟糕,会对性能产生巨大影响。对于 Hibernate,这意味着它需要在每次执行插入时发出 select nextval
。换句话说,每个插入都有 2 个 SQL 语句。
注意 2:另外请记住,您还需要保持 SequenceGenerator
的 initialValue
和序列的 startValue
同步. allocationSize
和 initialValue
是序列生成器用来计算下一个值的两个值。
注3:值得一提的是,根据生成序列的算法(hi-lo, pooled, pooled-lo等),service/application之间可能会出现间隙重新启动。
有用的资源:
- Hibernate pooled and pooled-lo identifier generators - 如果您希望更改序列生成器用于计算下一个值的算法。可能存在这样的情况(例如,在并发环境中),两个服务使用相同的数据库序列来生成值,并且它们生成的值可能会发生冲突。对于这种情况,一种策略优于另一种策略。
我有一个具有以下 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。
我是不是用错了发电机?
您需要将 SequenceGenerator
的 allocationSize
与序列的 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 日志,您将看到以下内容:
- 第一次调用
repository.save(entity)
会生成
select nextval('sequence_name`);
insert into table_name(...) values (...);
- 用
repository.save(entity)
保存第二个实体只会生成
insert into table_name(...) values (...);
- 插入
allocationSize
次后,您将再次看到:
select nextval('sequence_name`);
insert into table_name(...) values (...);
使用序列的优点是最大限度地减少了 Hibernate 需要与数据库对话以获取下一个 ID 的次数。根据您的使用情况,您可以调整 allocationSize
以获得最佳结果。
注意:评论之一建议使用allocationSize = 1
,这很糟糕,会对性能产生巨大影响。对于 Hibernate,这意味着它需要在每次执行插入时发出 select nextval
。换句话说,每个插入都有 2 个 SQL 语句。
注意 2:另外请记住,您还需要保持 SequenceGenerator
的 initialValue
和序列的 startValue
同步. allocationSize
和 initialValue
是序列生成器用来计算下一个值的两个值。
注3:值得一提的是,根据生成序列的算法(hi-lo, pooled, pooled-lo等),service/application之间可能会出现间隙重新启动。
有用的资源:
- Hibernate pooled and pooled-lo identifier generators - 如果您希望更改序列生成器用于计算下一个值的算法。可能存在这样的情况(例如,在并发环境中),两个服务使用相同的数据库序列来生成值,并且它们生成的值可能会发生冲突。对于这种情况,一种策略优于另一种策略。