休眠:脏场跟踪

Hibernate: Dirty Field Tracking

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

我有一个 Spring 启动应用程序,它当然有实体。这些实体通过 thymeleaf 形式更新。在这个表格中只有一些相关的字段是可以改变的。例如实体的namecreatedcreatedbylastUsedBy 等其他字段不受此表单控制/更改。

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

我仔细检查了实体是否得到了增强,还调试了 spring/hibernate 的整个保存过程。我在这里错过了什么吗?为什么 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。对我来说这看起来像是一个错误。

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

参见: @DynamicUpdate @DynamicInsert

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

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

因此,一旦您拥有实体,您只需设置通过 HTTP 请求发送的属性,然后您 let the dirty checking mechanism pick the changes

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

我找到了另一个更适合我需要的解决方案:问题毕竟不是 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 的第二个解决方案很接近,但省略了更改字段的繁琐手动映射。

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