休眠,@ SequenceGenerator和allocationSize


117

我们都知道使用Hibernate时的默认行为@SequenceGenerator-它使实际数据库序列增加,将该值乘以50(默认allocationSize值)-然后将该值用作实体ID。

这是错误的行为,并与说明以下内容的规范冲突:

distributionSize-(可选)从序列中分配序列号时要增加的数量。

需要说明的是:我不关心生成的ID之间的差距。

我关心与基础数据库序列不一致的 ID 。例如:任何其他应用程序(例如,使用纯JDBC)可能要在从序列获得的ID下插入新行-但是所有这些值可能已被Hibernate使用!疯狂。

有人知道任何解决此问题的方法(没有设置allocationSize=1,从而降低性能)吗?

编辑:
弄清楚。如果最后插入的记录的ID = 1,则HB同时51, 52, 53...在其新实体BUT中使用值:数据库中序列的值将设置为2。当其他应用程序使用该序列时,很容易导致错误。

另一方面:规范说(据我所知)应该将数据库序列设置为51,同时HB应该使用范围内的值 2, 3 ... 50


更新:
正如下面的史蒂夫·埃伯索尔(Steve Ebersole)所述:我所描述的行为(也是许多人中最直观的行为)可以通过设置启用hibernate.id.new_generator_mappings=true

感谢大家。

更新2:
对于将来的读者,您可以在下面找到一个有效的示例。

@Entity
@Table(name = "users")
public class User {

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

persistence.xml

<persistence-unit name="testPU">
  <properties>
    <property name="hibernate.id.new_generator_mappings" value="true" />
  </properties>
</persistence-unit>

2
“未设置allocationSize = 1并因此降低了性能”为什么将其设置为1会降低性能?
sheidaei 2012年

3
@sheidaei请参阅以下注释:-)这是因为每个人都save需要查询数据库以获取序列的下一个值。
G. Demecki 2012年

谢谢您也面临同样的问题。最初,我在每个@SequenceGenerator处添加allocationSize = 1。使用hibernate.id.new_generator_mappings = true可以防止这种情况。尽管JPA仍然查询数据库以获取每个插入的ID ...
TheBakker

1
使用SequenceGeneratorHibernate仅在由指定的ID数量allocationsize用完时才查询数据库。如果进行了设置,allocationSize = 1这就是Hibernate向数据库查询每个插入的原因。更改此值,您就完成了。
G. Demecki

1
谢谢!该hibernate.id.new_generator_mappings设置是非常重要的。我希望这是默认设置,我不必花费太多时间来研究ID号为何变得疯狂。
LeOn-Han Li

Answers:


43

绝对清楚...您所描述的内容与规范没有任何冲突。该规范讨论的是Hibernate分配给您的实体的值,而不是实际存储在数据库序列中的值。

但是,可以选择要获取的行为。首先看到我的答复:是否有一种方法可以使用JPA批注和Hibernate动态选择@GeneratedValue策略? 这将为您提供基础知识。只要您设置为使用该SequenceStyleGenerator,Hibernate就会使用SequenceStyleGenerator中allocationSize的“池优化器”进行解释。“池优化器”用于允许在创建序列时使用“增量”选项的数据库(并非所有支持序列的数据库都支持增量)。无论如何,请阅读那里的各种优化器策略。


谢谢史蒂夫!最好的答案。另外,您的其他帖子也很有帮助。
G. Demecki 2012年

4
我还注意到您是的合著者org.hibernate.id.enhanced.SequenceStyleGenerator。你让我惊讶
G. Demecki 2012年

22
你怎么惊讶?我是Hibernate的首席开发人员。我已经编写/共同编写了许多Hibernate课程;)
史蒂夫·埃伯索尔

仅作记录。应避免DB序列递增,以防止出现较大的间隔。DB序列乘以allocationSize当ID缓存运行out.More细节stackoverflow.com/questions/5346147/...
Olcay Tarazan

1
更改全局使用的“优化程序”的一种方法是在休眠选项中添加以下内容:serviceBuilder.applySetting(“ hibernate.id.optimizer.pooled.preferred”,LegacyHiLoAlgorithmOptimizer.class.getName()); 您可以选择任何优化程序类来代替LegacyHiLoAlgorithOptimizer,它将成为默认类。这样应该可以更轻松地将所需的行为保留为默认状态,而无需更改所有注释。此外,请注意“池化”和“ hilo”优化器:当您的序列值从0开始导致负ID时,它们会产生奇怪的结果。
fjalvingh

17

allocationSize=1这是一个微优化,在获取查询之前,Hibernate尝试在分配大小范围内分配值,因此尽量避免查询数据库的序列。但是,如果将查询设置为1,则每次都会执行此查询。这几乎没有什么区别,因为如果您的数据库被某个其他应用程序访问,那么如果另一个应用程序同时使用相同的ID则会产生问题。

序列ID的下一代基于分配大小。

通过默认,它被保持为50过多。如果您要50在一个会话中接近记录,而这些记录不会被持久化,并且使用该特定会话和事务来持久化,则它也将有所帮助。

因此,您应该在使用allocationSize=1时始终使用SequenceGenerator。对于大多数基础数据库,序列始终以递增1


12
与性能无关吗?你真的确定吗?有人告诉我,使用allocationSize=1Hibernate进行的每个save操作都需要执行到数据库的操作,以获得新的ID值。
G. Demecki 2012年

2
这是在获取查询之前的微优化,Hibernate尝试在范围内分配值allocationSize,因此尽量避免查询数据库的序列。但是,该查询每次如果你把它设置为1时这几乎没有什么差别,因为如果你的数据库是通过一些其他的应用程序访问,如果相同的ID所使用的同时另一个应用程序的话就会产生问题被执行
阿米特·德什潘德

是的,分配大小为1是否会对实际性能产生影响完全取决于应用程序。当然,在微观基准中,它总是会产生巨大的影响。这就是大多数基准测试(微型或其他基准)的问题,它们根本是不现实的。即使它们足够复杂以至于有些现实,您仍然必须查看基准与实际应用程序之间的距离,以了解基准测试结果对您在应用程序中看到的结果的适用性。长话短说..为您自己进行测试
史蒂夫·埃伯索尔

2
好。一切都是特定于应用程序的,不是吗!如果您的应用程序是只读应用程序,则使用分配大小1000或1的影响绝对为0。另一方面,类似的事情是最佳实践。如果您不尊重最佳实践,那么它们会聚集在一起,并且综合影响将使您的应用程序变得迟钝。另一个示例是当您完全不需要某个事务时开始进行事务。
哈桑·塞兰

1

史蒂夫·埃伯索尔(Steve Ebersole)和其他成员,
能否请您解释ID差距较大(默认值为50)的原因?我正在使用Hibernate 4.2.15,并在org.hibernate.id.enhanced.OptimizerFactory cass中找到以下代码。

if ( lo > maxLo ) {
   lastSourceValue = callback.getNextValue();
   lo = lastSourceValue.eq( 0 ) ? 1 : 0;
   hi = lastSourceValue.copy().multiplyBy( maxLo+1 ); 
}  
value = hi.copy().add( lo++ );

每当它到达if语句的内部时,hi值就会变得更大。因此,在频繁重启服务器的测试过程中,我的ID生成了以下序列ID:
1、2、3、4、19、250、251、252、400、550、750、751、752、850、1100、1150。

我知道您已经说过它与规范没有冲突,但是我相信对于大多数开发人员来说这将是非常出乎意料的情况。

任何人的意见都会很有帮助。

吉万

更新:ne1410s:感谢您的编辑。
cfrick:好的。我去做。这是我在这里的第一篇文章,不确定如何使用。

现在,我更好地理解了为什么将maxLo用于两个目的:由于休眠一次调用数据库序列,因此在Java级别中不断增加id并将其保存到DB中,因此Java级别的id值应考虑不调用而更改了多少DB序列下次调用该序列时。

例如,某个点的序列ID为1,然后休眠输入5、6、7、8、9(分配大小= 5)。下次,当我们获得下一个序列号时,DB返回2,但是休眠需要使用10、11、12 ...因此,这就是为什么“ hi = lastSourceValue.copy()。multiplyBy(maxLo + 1)”是用于从数据库序列返回的2中获取下一个ID 10。在频繁的服务器重启过程中,似乎只有一件麻烦的事,这是我的问题所在,即间隙更大。

因此,当我们使用SEQUENCE ID时,表中插入的ID将与DB中的SEQUENCE号不匹配。


1

深入研究休眠源代码后,在进行50次插入后,下面的配置进入Oracle db以获取下一个值。因此,使您的INST_PK_SEQ每次调用增加50。

Hibernate 5用于以下策略

还要在下面检查 http://docs.jboss.org/hibernate/orm/5.1/userguide/html_single/Hibernate_User_Guide.html#identifiers-generators-sequence

@Id
@Column(name = "ID")
@GenericGenerator(name = "INST_PK_SEQ", 
strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
parameters = {
        @org.hibernate.annotations.Parameter(
                name = "optimizer", value = "pooled-lo"),
        @org.hibernate.annotations.Parameter(
                name = "initial_value", value = "1"),
        @org.hibernate.annotations.Parameter(
                name = "increment_size", value = "50"),
        @org.hibernate.annotations.Parameter(
                name = SequenceStyleGenerator.SEQUENCE_PARAM, value = "INST_PK_SEQ"),
    }
)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "INST_PK_SEQ")
private Long id;

3
抱歉,但这是一种非常冗长的设置方式,可以很容易地用两个参数表示整个Hibernate,从而表示所有实体。
G. Demecki

是的,但是当我尝试其他方法时,如果您使用的所有方法都无法正常工作,则可以向我发送您的配置信息
fatih tekin

我更新了我的答案-现在它还包含一个有效的示例。尽管我上面的评论部分是错误的:不幸的是,您不能为所有实体既不设置allocationSize也不initialValue全局设置(除非仅使用一个生成器,但是恕我直言,它不是很可读)。
G. Demecki

1
感谢您的解释,但是您在上面所写的内容我已经尝试过了,并且在hibernate 5.0.7中不起作用。在休眠源代码中。配置可能看起来很糟糕,但是不幸的是它是休眠的api,而我使用的是休眠的标准EntityManager实现
fatih tekin

1

我在Hibernate 5中也遇到了这个问题:

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE)
@SequenceGenerator(name = SEQUENCE, sequenceName = SEQUENCE)
private Long titId;

收到以下警告:

发现已使用不推荐使用的基于序列的[org.hibernate.id.SequenceHiLoGenerator] ID生成器;请改用org.hibernate.id.enhanced.SequenceStyleGenerator。有关详细信息,请参见《 Hibernate域模型映射指南》。

然后将我的代码更改为SequenceStyleGenerator:

@Id
@GenericGenerator(name="cmrSeq", strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
        parameters = {
                @Parameter(name = "sequence_name", value = "SEQUENCE")}
)
@GeneratedValue(generator = "sequence_name")
private Long titId;

这解决了我的两个问题:

  1. 已弃用的警告已修复
  2. 现在,根据oracle序列生成了id。

0

我将在DDL中检查模式中的序列。JPA实施仅负责创建具有正确分配大小的序列。因此,如果分配大小为50,则您的序列的DDL中的增量必须为50。

这种情况通常可能发生在创建分配大小为1的序列,然后将其配置为分配大小为50(或默认值)但不更新序列DDL的情况下。


你误会了我的意思。ALTER SEQUENCE ... INCREMENTY BY 50;不会解决任何问题,因为问题仍然存在。序列值仍然不能反映真实的实体ID。
G. Demecki 2012年

请分享一个测试案例,以便我们在这里更好地理解问题。
哈桑·塞兰

1
测试用例?为什么?我发布的问题并没有那么复杂,并且已经得到解答。看来您不知道HiLo生成器的工作原理。无论如何:感谢您牺牲您的时间和精力。
G. Demecki 2012年

1
格雷戈里,实际上我确实知道我在说什么,我写了Batoo JPA,它是%100 JPA实现,目前正在孵化中,在速度方面比Hibernate快15倍。另一方面,我可能误解了您的问题,并且不认为将Hibernate与序列结合使用根本不会造成任何问题,因为自2003年以来我在许多数据库的许多项目中使用Hibernate。重要的是您已经解决了这个问题,对不起,我错过了标为正确答案的答案……
Hasan Ceylan 2012年

抱歉,我不是要冒犯您。再次感谢您的帮助,问题已得到解答。
G. Demecki 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.