Hibernate 尝试在设置 parent 后坚持两次 child

Hibernate tries to persist child twice after setting parent

我有这样的 Hibernate 映射:

Parent:

@OneToMany(mappedBy = "parent")
@Valid
private Set<Assignment> assignments;

Child:

@ManyToOne(cascade = {CascadeType.PERSIST})
@JoinColumn(name = "parent_id")
@Valid
private Parent parent;

我有这样的情况:Parent 已经保存在数据库中,我得到了新的作业列表。我做了这样的事情(使用 Spring Data JPA):

parent.getAssignments().addAll(newAssignments);
parent.getAssignments().forEach(assignment -> assignment.setParent(parent));
parentRepository.save(parent);

我在基本实体中有@PrePersist 注释方法(由两者扩展),验证检查在@PrePersist 中初始化的createdDate 是否不为空。基本实体包含:

@NotNull
@Column(name = "created_date")
protected LocalDateTime createdDate;

@PrePersist
public void prePersist() {
    setCreatedDate(LocalDateTime.now());
    setModifiedDate(LocalDateTime.now());
    validateEntity();
}

private void validateEntity() {
    Set<ConstraintViolation<BaseEntity>> violations = Validation.buildDefaultValidatorFactory().getValidator().validate(this);
    if(!violations.isEmpty()) {
        throw new RuntimeException(new ValidationMessageBuilder().build(violations));
    }
}

我已经检查了调试器下的 objects,例如赋值是 object @17609,Parent 是 @17608。

调用 save() 方法后引发异常。

java.lang.RuntimeException: may not be null => com.foo.bar.model.domain.Assignment.parent.assignments.createdDate.
at com.foo.bar.model.BaseEntity.validateEntity(BaseEntity.java:70)
at com.foo.bar.model.BaseEntity.prePersist(BaseEntity.java:58)

我已经调试并在 Assignment 实体的 @PrePersist 方法中 Hibernate 为另一个 object 调用它,它是 Assignment@17646,其中包含 Parent@17608 和 parent包含 children 个元素的集合,其中一个元素是 @17609.

为什么 hibernate 会尝试持久化另一个 object?

这些是我对你的问题的观察:

  1. 抛出的异常不是来自 Hibernate... 由您的验证方法抛出:validateEntity(),因为您的 child 作业有 null createDate.

  2. 您正在使用 EntityListeners 设置您的 createdDatemodifiedDate 并验证您的 bean 值...但是,看起来 (这是我的猜测)您的验证方法正在迭代 (并验证)在休眠之前很久的 child 赋值 对它们执行 @PrePersist

我的解决问题的建议:

  1. 使用这个配置:

Parent:

@OneToMany(mappedBy = "parent", CascadeType = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE})
private Set<Assignment> assignments;

Child:

@ManyToOne
@JoinColumn(name = "parent_id")
private Parent parent;

BaseClass:将您的 prePersist 方法重命名为:

@PrePersist
@PreUpdate
public void prePersistOrUpdate() {
    setCreatedDate(LocalDateTime.now());
    setModifiedDate(LocalDateTime.now());
    validateEntity();
}
  1. 检查(并根据需要修改)您的基本验证方法。这个 方法应该只验证实例变量(如 createdDate) 并避免横向 child objects 稍后为 EntityListener 方法修改: prePersistOrUpdate()(注意,我删除了 @Valid 注释 因为我不知道它的用途...可能是它被 你的验证框架)。

  2. 修改业务逻辑(其中newAssigments)被持久化 像这样:

    parent.getAssignments().addAll(newAssignments); parent.getAssignments().forEach(assignment -> assignment.setParent(parent)); parentRepository.merge(parent); // This should cascade and persist the new assigments!