【问题标题】:Hibernate: Dirty Field TrackingHibernate:脏场跟踪
【发布时间】:2017-03-03 14:46:39
【问题描述】:

我现在有点困惑。我会尽量简单地解释我的担忧:

我有一个 Spring Boot 应用程序,当然,它有实体。这些实体通过百里香叶形式更新。在这种形式中,只有一些相关字段是可更改的。例如实体的name。 created、createdby 或 lastUsedBy 等其他字段不受此表单的控制/更改。

现在问题来了:如果我们现在更改实体,所有其他字段都设置为null,因为它们不在请求中。一种方法是为这些缺失的字段添加<input type="hidden"/>inputs。但这不是很优雅且容易出错。 比我得出的结论,休眠应该只更新那些已更改的字段。所以这必须通过 DirtyTracking 来完成。我目前有另一个应用程序,它使用 OpenJPA 和 openJPA 增强器,在这个应用程序中,更新只更新更改的字段。我的假设是 Hibernate 增强器可以解决我的问题。但是即使启用了脏跟踪,所有字段都会更新并且信息会丢失。 当我将@DynamicUpdateannotation 添加到给定实体时,我已经设法让它工作,但这不是正确的方法吗?

我仔细检查了实体是否得到了增强,还调试了 spring/hibernate 的整个保存过程。我在这里错过了什么吗?为什么休眠还会更新所有非脏字段?

编辑

我进一步检查并得出这一点:代码来自 AbstractEntityTuplizer

public void setPropertyValues(Object entity, Object[] values) throws HibernateException {
    boolean setAll = !entityMetamodel.hasLazyProperties();

    for ( int j = 0; j < entityMetamodel.getPropertySpan(); j++ ) {
        if ( setAll || values[j] != LazyPropertyInitializer.UNFETCHED_PROPERTY ) {
            setters[j].set( entity, values[j], getFactory() );
        }
    }
}

values 对象数组只有几个预填充的值,只有更改/脏的值。例如values[5] 和values[6]。但是所有的设置器都会被调用,并且如果它们没有出现在函数的值参数中,它们的值将设置为null。对我来说看起来像是一个错误。

【问题讨论】:

  • FWIW DataNucleus JPA 也使用字节码增强,增强合约将跟踪字段何时变脏(只需查看反编译的增强类了解如何),因此只会更新需要更新的字段。它使用的字节码增强合约源自 JDO 规范(OpenJPA 多年前也从该规范派生)

标签: java spring hibernate jpa byte-code-enhancement


【解决方案1】:

使用@DynamicUpdate 是可行的方法,当实体使用@DynamicUpdate 注释时,在更新阶段,hibernate 不会为您未更改的列发出更新语句。这样,当您保存实体时,created、createdby 和 lastUsedBy 将不会更改。

见: @DynamicUpdate @DynamicInsert

【讨论】:

  • 还有其他解决方案吗?我的意思是,我必须增强一切并为每个实体添加注释? OpenJPA 如何设法让它运行,但 Hibernate 却无法做到这一点?
  • Hibernate 让您可以选择如何管理这些“未触及”的字段。跟踪这些字段会产生成本损失,因此 hibernate 选择默认关闭此选项。
  • 是的,好的。我明白那个。但是当我没有增强实体时,@DynamicUpdate 注释什么也不做。我错过了什么吗?
【解决方案2】:

我找到了另一个更适合我需要的解决方案:问题毕竟不是 Hibernate,而是 Spring MVC。

之前的一些代码:

@PutMapping("/{oid}/edit")
public String update(@ModelAttribute("oid") @Valid T entity,
                     BindingResult result,
                     Model model,
                     HttpServletRequest request)

关键是这里的@ModelAttribute("oid")。在我没有“oid”之前。所以 Spring 使用的是从表单发布的模型,显然没有完全填写。仅使用表单输入。 通过将“oid”添加到注释中,Spring MVC 和 Spring Data 在应用表单字段之前获取实体。所以我们有一个完整的实体,我们可以保存它。

此解决方案接近于 @Vlad 的第二个解决方案,但省略了更改字段的繁琐手动映射。

也许我必须为其他人提供更多信息才能全面了解我的问题!否则,谢谢!

【讨论】:

    【解决方案3】:

    最简单的方法是将修改合并到实体上:

    1. 在两个 HTTP 请求之间保存在 HttpSession 或 Redis(例如 Spring Session)中。在这种情况下,请确保您merge the detached entity。
    2. 根据标识符在第二个 HTTP 请求中获取

    所以,一旦你有了实体,你只需设置你通过 HTTP 请求发送的属性,然后let the dirty checking mechanism pick the changes。

    这样,您就不需要@DynamicUpdate,这应该适用于任何 JPA 提供程序。

    【讨论】:

    • 如果您打开了多个浏览器窗口,则通过会话使用您的第一种方法可能会导致问题。我对这样的解决方法也不太满意。您的第二种方法是 IMO 不是很优雅,也是一种解决方法。感谢您的回答,但那里没有任何“正确”的方式吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-26
    • 1970-01-01
    • 2015-04-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多