【问题标题】:Hibernate, @SequenceGenerator and allocationSize休眠、@SequenceGenerator 和 allocationSize
【发布时间】:2012-09-26 13:55:38
【问题描述】:

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

这是不正确的行为,并与 specification 冲突,它说:

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

明确一点:我不关心生成的 ID 之间的差距。

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

有人知道这个问题的任何解决方案吗(不设置allocationSize=1 从而降低性能)?

编辑:
把事情说清楚。 如果最后插入的记录的 ID = 1,则 HB 同时为其新实体使用值 51, 52, 53...:数据库中的序列值将设置为 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>

【问题讨论】:

  • "没有设置 allocationSize=1 从而降低性能"为什么它会降低性能是你设置为 1 吗?
  • @sheidaei 请参阅下面的评论 :-) 这是因为每个save 都需要查询数据库以获取序列的下一个值。
  • 谢谢你遇到了同样的问题。起初,我在每个 @SequenceGenerator 中添加了 allocationSize = 1。使用 hibernate.id.new_generator_mappings=true 可以防止这种情况。虽然 JPA 仍然查询数据库以获取每个插入的 id ...
  • 使用SequenceGenerator,只有当allocationsize 指定的ID 数量用完时,Hibernate 才会查询数据库。如果您设置了allocationSize = 1,那么这就是 Hibernate 为每个插入查询数据库的原因。改变这个值,你就完成了。
  • 谢谢! hibernate.id.new_generator_mappings 设置非常重要。我希望这是默认设置,我不必花太多时间研究为什么 id 号码会变得疯狂。

标签: java hibernate jpa hilo


【解决方案1】:

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

但是,可以选择获取您正在寻找的行为。首先查看我在Is there a way to dynamically choose a @GeneratedValue strategy using JPA annotations and Hibernate? 上的回复,这将为您提供基础知识。只要您设置为使用该 SequenceStyleGenerator,Hibernate 就会使用 SequenceStyleGenerator 中的“池化优化器”解释allocationSize。 “池化优化器”用于允许在创建序列时使用“增量”选项的数据库(并非所有支持序列的数据库都支持增量)。无论如何,请阅读那里的各种优化器策略。

【讨论】:

  • 谢谢史蒂夫!最佳答案。您的其他 post 也很有帮助。
  • 我还注意到您是org.hibernate.id.enhanced.SequenceStyleGenerator 的合著者。你让我大吃一惊。
  • 让你吃惊吗?我是 Hibernate 的首席开发人员。我编写/共同编写了许多 Hibernate 类;)
  • 仅作记录。应避免 DB 序列增量以防止出现较大的间隙。当 ID 缓存用完时,DB 序列与 allocationSize 相乘。更多详情stackoverflow.com/questions/5346147/…
  • 更改全局使用的“优化器”的一种方法是在您的休眠选项中添加类似这样的内容: serviceBuilder.applySetting("hibernate.id.optimizer.pooled.preferred", LegacyHiLoAlgorithmOptimizer.class.getName ());您可以选择任何优化器类,而不是 LegacyHiLoAlgorithOptimizer,它将成为默认值。这应该更容易将您想要的行为保留为默认行为,而无需更改所有注释。此外,请注意“pooled”和“hilo”优化器:当您的序列值从 0 开始导致负 ID 时,它们会给出奇怪的结果。
【解决方案2】:

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

下一代Sequence Id基于allocationSize。

默认情况下,它被保留为50,这太多了。如果您将在一个会话中拥有接近 50 的记录,这些记录不会被持久化并且将使用这个特定的会话和事务进行持久化,它也只会有所帮助。

因此,在使用SequenceGenerator 时,您应该始终使用allocationSize=1。对于大多数底层数据库序列总是增加1

【讨论】:

  • 与性能无关?你真的确定吗?我被告知,在每个save 操作上使用allocationSize=1 Hibernate 都需要访问数据库以获得新的ID 值。
  • 这是在获取查询之前的一个微优化 Hibernate 尝试在allocationSize 的范围内分配值,因此尽量避免查询数据库的序列。但是,如果您将其设置为 1,则每次都会执行此查询。这几乎没有什么区别,因为如果您的数据库被其他应用程序访问,那么如果同时另一个应用程序使用相同的 id,则会产生问题
  • 是的,分配大小为 1 是否具有任何实际性能影响完全取决于应用程序。当然,在微观基准测试中,它总是会产生巨大的影响。这就是大多数基准(微型或其他)的问题,它们根本不现实。即使它们足够复杂以至于有点现实,您仍然需要查看基准测试与您的实际应用程序的接近程度,以了解基准测试结果与您在应用程序中看到的结果的适用程度。长话短说..自己测试一下
  • 好的。一切都是特定于应用程序的,不是吗!如果您的应用程序是只读应用程序,那么使用分配大小 1000 或 1 的影响绝对是 0。另一方面,这些都是最佳实践。如果您不尊重他们收集的最佳实践,那么综合影响将是您的应用程序变得迟缓。另一个例子是在您绝对不需要时开始交易。
【解决方案3】:

我会在 DDL 中检查模式中的序列。 JPA 实现只负责创建具有正确分配大小的序列。因此,如果分配大小为 50,那么您的序列在其 DDL 中必须有 50 的增量。

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

【讨论】:

  • 你误解了我的意思。 ALTER SEQUENCE ... INCREMENTY BY 50; 不会解决任何问题,因为问题仍然存在。序列值仍然不能反映真实的实体 ID。
  • 请分享一个测试用例,以便我们更好地理解这里的问题。
  • 测试用例?为什么?我发布的问题并没有那么复杂,并且已经得到了回答。看来你不知道 HiLo 生成器是如何工作的。无论如何:感谢您牺牲您的时间和精力。
  • Gregory,实际上我知道我在说什么,我已经编写了 Batoo JPA,它是目前处于孵化阶段的 %100 JPA 实现,在速度方面比 Hibernate 快 15 倍。另一方面,我可能误解了您的问题,并且认为将 Hibernate 与序列一起使用不会产生任何问题,因为自 2003 年以来,我在许多数据库的许多项目中都使用了 Hibernate。重要的是你得到了问题的解决方案,抱歉我错过了标记为正确的答案......
  • 对不起,我不是故意冒犯你的。再次感谢您的帮助,问题已得到解答。
【解决方案4】:

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:感谢您的编辑。
克里克:好的。我去做。这是我在这里的第一篇文章,不知道如何使用它。

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

例如,序列 id 在某个点为 1,而休眠输入 5、6、7、8、9(其中 allocationSize = 5)。下一次,当我们得到下一个序列号时,DB返回2,但是hibernate需要使用10、11、12......所以,这就是为什么“hi = lastSourceValue.copy().multiplyBy(maxLo+1)”是用于从 DB 序列返回的 2 中获取下一个 id 10。似乎唯一令人烦恼的事情是在频繁的服务器重启期间,这是我与较大差距的问题。

所以,当我们使用 SEQUENCE ID 时,表中插入的 id 将与 DB 中的 SEQUENCE 编号不匹配。

【讨论】:

    【解决方案5】:

    在深入研究 hibernate 源代码和 下面的配置在 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;
    

    【讨论】:

    • 抱歉,这是一种极其冗长的设置方式,可以很容易地用两个参数来表示整个 Hibernate 以及所有实体。
    • 是的,但是当我尝试其他方法时,如果你能正常工作,它们都不起作用,可以向我发送你的配置方式
    • 我已经更新了我的答案 - 现在它还包括一个工作示例。尽管我上面的评论部分错误:不幸的是,您不能为所有实体全局设置allocationSizeinitialValue(除非只使用一个生成器,但恕我直言,它不是很可读)。
    • 感谢您的解释,但是您在上面写的内容我已经尝试过,但它不适用于休眠 5.0.7.Final 版本,然后我已经深入研究了源代码以实现这个目标是我能够在休眠源代码中找到的实现。配置可能看起来很糟糕,但不幸的是 hibernate api,我正在使用 hibernate 的标准 EntityManager 实现
    【解决方案6】:

    我在 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. 现在id是按照oracle序列生成的。

    【讨论】:

    • 只要您告诉 Hibernate 使用它的“新生成器”,您就可以使用原始映射 - 将 hibernate.id.new_generator_mappings 设置为 true。并主动回答我所看到的后续问题......请记住,Hibernate 实际上已经有 20 年历史了,并且有许多现有的部署,我们不能仅仅改变 id 生成的方式默认情况下 在不破坏所有现有应用程序的情况下...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-23
    • 1970-01-01
    • 2011-02-20
    • 1970-01-01
    • 2014-09-15
    • 2012-06-26
    相关资源
    最近更新 更多