【问题标题】:Child entity is not getting inserted/updated on parent entity update with onetomany子实体未在使用 onetomany 的父实体更新时插入/更新
【发布时间】:2020-04-19 11:10:57
【问题描述】:

我正在使用@OneToMany 注释来保存父实体和子实体,但在特定情况下保存子实体时遇到问题。

子实体在两种情况下被保存:

  1. 在第一次插入有孩子的父母时。
  2. 当数据库中没有插入/保存子项时,在更新父项和子项期间 在第一次插入中

但是当父级与子级 1 一起插入,然后在父级更新期间我尝试插入子级 2 时,我无法保存子级 2

它失败了,但有以下异常:

o.h.e.jdbc.spi.SqlExceptionHelper - ORA-01407: cannot update ("app_Name"."Child_entity"."REFERENCE_ID") to NULL\n 
23:22:06.068 ERROR o.h.i.ExceptionMapperStandardImpl - HHH000346: Error during managed flush [org.hibernate.exception.ConstraintViolationException: could not execute statement] 

请看我的代码如下:

@Data
@Entity
@Table("Parent_table")
public class Parent_entity implements Serializable {


    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval =true)
    @JoinColumn(name="REFERENCE_ID")
    private Set<Child_Entity> childrens ;

    }


@Data
@Entity
@Table("child_table")
public class Child_entity implements Serializable {

    @Id
    @GeneratedValue(generator = "seq_gen", strategy = GenerationType.SEQUENCE)
    @SequenceGenerator(name = "seq_gen", sequenceName = "child_SEQ",allocationSize=1)
    @Column(name ="col_name")
    private Integer asSeq;

    @Column(name ="REFERENCE_ID")
    private String referenceid;

    }   

在映射器类中,我明确设置了父表的主键。

Oracle 数据库端我添加了以下外键约束

  ALTER TABLE child_table
  ADD CONSTRAINT FK_parent_table
  FOREIGN KEY (REFERENCE_ID)
  REFERENCES Parent_table(REFERENCE_ID);

我浏览过关于 stackoverflow 的类似问题,这表明如果您使用现有列作为外键,则该列的现有值不应为空。 但在我的情况下,列“REFERENCE_ID”已经不可为空。

如果我需要添加其他内容以使其正常工作,请告诉我或建议。

谢谢。

编辑: 在更新场景中,Hibernate 正在生成以下查询:

update child_table set reference_id=null where reference_id=? and child_seq=?

其中reference_id是Parent的主键,child_seq是Child的主键

知道为什么 hibernate 会尝试更新 Parent 的主键 我在子实体中明确设置父主键的值

【问题讨论】:

    标签: spring-data-jpa parent-child one-to-many


    【解决方案1】:

    这里其实存在三个问题:

    1. 显然,“更新方案”插入了两个新的孩子,而不是保留一个并添加一个。
    2. 具有不可为空连接列的单向 OneToMany 关系
    3. Lo​​mbok 生成的 equals 和 hashCode

    TL;TR:转到 2

    1.更新场景

    Hibernate 正在尝试将 reference_id 更新为 NULL,因为它想从父实体中“分离”一个子实体。这意味着,在更新期间,您将添加两个新子代,而不是保留一个并添加一个。我还没有看到你的相关代码,但我认为它或多或少看起来像这样:

    ParentEntity parent = new ParentEntity();
    parent.setId("test");
    ChildEntity child1 = new ChildEntity();
    child1.setReferenceid(parent.getId());
    parent.setChildrens(new HashSet<>(Arrays.asList(child1)));
    repository.save(parent);
    
    ChildEntity child2 = new ChildEntity();
    child2.setReferenceid(parent.getId());
    parent.getChildrens().add(child2);
    repository.save(parent);
    

    它以ConstraintViolationException 结尾。在第二个save 调用中,child1 仍然是一个“分离”的实例, 它的 id 是 NULL 并且 Hibernate 将这两个孩子视为新的。所以首先它将它们添加到child_table,然后尝试删除“旧”的, 通过将其 referenceId 设置为 NULL (孤儿删除稍后发生,并且有点不相关)。 它可以很容易地修复:

    // ...
    parent = repository.save(parent); // <- save(parent) returns updated object
    
    ChildEntity child2 = new ChildEntity();
    child2.setReferenceid(parent.getId());
    parent.getChildrens().add(child2);
    repository.save(parent);
    

    不再有例外,但这并不能解决问题。迟早你会从孩子集中移除一个孩子,然后 它总是会导致异常。

    2。具有不可为空连接列的单向 OneToMany 关系

    典型的建模方式如下:

    父实体

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
    @JoinColumn(name = "REFERENCE_ID", nullable = false)
    private Set<ChildEntity> childrens;
    

    子实体

    @Column(name = "REFERENCE_ID", insertable = false, updatable = false)
    private String referenceid;
    

    它应该可以工作,但 Hibernate 会生成不必要的“更新”查询:

    select parententi0_.parent_id as parent_i1_1_1_, childrens1_.reference_id as referenc3_0_3_, childrens1_.id as id1_0_3_, childrens1_.id as id1_0_0_, childrens1_.name as name2_0_ ...
    select nextval ('child_seq')
    select nextval ('child_seq')
    insert into child_table (name, reference_id, id) values (?, ?, ?)
    insert into child_table (name, reference_id, id) values (?, ?, ?)
    update child_table set reference_id=? where id=?
    update child_table set reference_id=? where id=?
    delete from child_table where id=?
    

    一两个项目没什么大不了的,但有 100 个? 发生这种情况是因为 ParentEntity 是 关系的所有者(由于 @JoinTable 注释)。它对 child_table 外键一无所知,也不知道如何处理它。

    2b。 “一半”双向 OneToMany 关系

    或者,我们可以尝试通过删除 @JoinColumn 并添加 mappedBy 来使 ChildEntity 成为关系的所有者。理论上,关系的另一边应该有一个对应的@ManyToOne,但没有它似乎也可以工作。此解决方案是最佳的,但可能不可移植(对于不同的 JPA 提供程序或不同的 Hibernate 版本)。

    父实体

    @OneToMany(mappedBy = "referenceid", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
    private Set<ChildEntity> childrens;
    

    ChildEntity(没有变化,与问题相同)

    @Column(name = "REFERENCE_ID")
    private String referenceid;
    

    更新时 Hibernate 生成以下查询:

    select parententi0_.parent_id as parent_i1_1_1_, childrens1_.reference_id as referenc3_0_3_, childrens1_.id as id1_0_3_, childrens1_.id as id1_0_0_, childrens1_.name as name2_0_ ...
    select nextval ('child_seq')
    select nextval ('child_seq')
    insert into child_table (name, reference_id, id) values (?, ?, ?)
    insert into child_table (name, reference_id, id) values (?, ?, ?)
    delete from child_table where id=?
    

    3. Lombok 生成的 equals 和 hashCode

    这与您的问题没有直接关系,但我认为您迟早会遇到这个问题。您正在使用 @Data 注释(我假设它们是 Lombok 的,如果不是,请忽略本节)。默认情况下,它们将从所有字段(包括 id)生成 equalshashCode 方法。在 ParentEntity 中很好,其中 id 是手动设置的。但是在由数据库生成 id(又名asSeq)的 ChildEntity 中,它破坏了 hashCode()/equals() 合同。它可能会导致真正偷偷摸摸的错误。来自 Hibernate 文档:

    这里的问题是生成标识符的使用、Set 的契约和 equals/hashCode 实现之间的冲突。 Set 表示当对象是 Set 的一部分时,对象的 equals/hashCode 值不应更改。但这正是这里发生的情况,因为 equals/hasCode 是基于(生成的)id,在提交 JPA 事务之前没有设置。

    您可能想在此处阅读更多相关信息:

    【讨论】:

      猜你喜欢
      • 2013-07-24
      • 1970-01-01
      • 1970-01-01
      • 2019-12-05
      • 2021-02-09
      • 1970-01-01
      • 2016-11-16
      • 2011-12-19
      • 1970-01-01
      相关资源
      最近更新 更多