【问题标题】:@PreUpdate doesn't save parent object when it's updated@PreUpdate 更新时不保存父对象
【发布时间】:2018-01-22 13:52:54
【问题描述】:

我有两个具有一对多关系的实体。 Parent 可以有多个 Child 实体实例。 我向父项添加了一个字段,用于存储子项修改的日期(childrenLastModifiedDate)。为了保持这一点,我添加了方法:

@PrePersist
@PreUpdate
@PreRemove
private void handle() {
    parent.setChildrenLastModifiedDate(now());
}

这就是问题所在。当孩子被保存时,它并不总是被调用。 在本地(mac os),它按预期工作,所有三种类型的更改都被跟踪并保存到父实体。但是,在服务器(linux)上,它仅适用于:

  • @PrePersist
  • @PreRemove

即使调用了 PreUpdate,也不会保存更改。我什至尝试添加直接存储库调用来保存父级。结果是一样的。更新时没有保存任何内容,但删除或保留时保存。 我尝试在附加事务中进行此更改并且它有效,但它太消耗资源了。另外,我可以看到有人有非常相似的经历:

JPA/Hibernate preUpdate doesn't update parent object

但是,没有关于如何处理问题本身的内容。 问题是:有没有办法保证使用此注释将始终有效并执行依赖实体的附加更新?如果不是,那么处理这种逻辑的最佳方法是什么?对子项的每次更改都需要更新。即使是通过级联保存。

【问题讨论】:

  • 是否可以添加评论并解释接受的答案如何解决问题。它与@EntityListeners 有关吗?

标签: java spring hibernate jpa spring-data


【解决方案1】:

我遇到了同样的问题。我使用 Hibernate Interceptor 修复了它。

首先,您必须在基础实体类中声明一些基础方法,以便我们找到实体的父级,这是一个链接更新所需字段的方法。

public abstract class BaseEntity {
    public Optional<BaseEntity> getParent() {
        // subclass should override this method to return the parent entity
        return Optional.empty();
    }
    public void updateChain() { 
        // update your desired fields
        ...
        this.getParent().ifPresent(p -> p.updateChain());
    }
}

然后您可以使用以下 Hibernate 拦截器来强制更新脏实体的所有父级。

public class EntityChainForceUpdateHibernateInterceptor extends EmptyInterceptor {

    @Override
    public void preFlush(Iterator entities) {
        entities.forEachRemaining(e -> {
            if (BaseEntity.class.isAssignableFrom(e.getClass())) {
                BaseEntity b = (BaseEntity) e;
                b.getParent().ifPresent(p -> p.updateChain());
            }
        });
    }
}

要向 Spring 注册此拦截器,只需将以下行添加到 application.properties

spring.jpa.properties.hibernate.ejb.interceptor=com.your.package.EntityChainForceUpdateHibernateInterceptor

这是一个如何覆盖getParent()的示例。

@MappedSuperclass
public abstract class BaseEntity {

    @Column(name = "last_updated_at")
    private Instant lastUpdatedAt;

    public Optional<BaseEntity> getParent() {
        return Optional.empty();
    }
    public void updateChain() {
        this.lastUpdatedAt = Instant.now();
        this.getParent().ifPresent(p -> p.updateChain());
    }
}

@Entity
public class ParentEntity extends BaseEntity {
    @Id
    @Column(name = "id")
    private String id;

    @Column(name = "something")
    private String somethingToUpdateWhenChildSaved;

    @Override
    public void updateChain() {
        this.somethingToUpdateWhenChildSaved = "anything";
        super.updateChain();
    }
}

@Entity
public class ChildEntity extends BaseEntity {

    @ManyToOne
    @JoinColumn(name = "parent_id", referencedColumnName = "id")
    private ParentEntity parent;

    @Override
    public Optional<BaseEntity> getParent() {
        return Optional.of(this.parent);
    }
}

【讨论】:

  • 你能举一个覆盖 getParent() 的例子吗?我被语法困住了。如何通过模型返回父对象?
  • @Leena 抱歉,我刚刚看到您的评论。我添加了示例。
【解决方案2】:

看来您的问题仅限于@PreUpdate

我个人的看法是如果没有调用@PreUpdate,你的实体就没有更新。但是你可以很容易地检查它。对于需要检查update 活动的实体,我建议使用乐观锁(仅使用version field following hibernate doc)或auditing。如果更新已处理,您肯定可以检测到 @PreUpdate 活动是否已处理。经过一些测试,您会发现问题出在哪里。

附言

如果@PreUpdate 同时用于实体和订阅@EntityListeners 实体,我从未检查过行为。至于我,冲突可能发生在这种情况下。不确定,但如果你有这两个注释,请检查复杂和单独的行为(谁知道,可能是休眠开发人员决定不可能的情况,因为如果在你的实体侦听器实现中声明了一些处理程序,你不需要实体中的 @PreUpdate 处理程序。如果你会检查它,请在评论中告诉我)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-25
    • 2013-02-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多