JPA插入父/子结果导致MySQLIntegrityConstraintViolationException

ven*_*syv 4 java mysql hibernate jpa

这已经被多次询问了,但是我找不到任何好的答案,所以我会再问一次.

我有父子单向关系如下:

@Entity
@Table(name = "PARENT")
public class Parent {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID")
    private Long parentId;

    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
    @JoinTable(name = "CHILD", joinColumns = @JoinColumn(name = "parent_id"), inverseJoinColumns = @JoinColumn(name = "ID"))
    private List<Child> children;
}

@Entity
@Table(name = "CHILD")
public class Child {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID")
    private Long id;

    @Column(name = "PARENT_ID")
    private Long parentId;

    //some other field
}
Run Code Online (Sandbox Code Playgroud)

我创建了一个父实例,为它分配一个子列表并尝试保留它:

Parent p = new Parent();
List<Child> children = new ArrayList<Child>();
Child c = new Child();
children.add(c);
p.addChildren(children);
em.merge(p);
Run Code Online (Sandbox Code Playgroud)

代码运行时,我得到以下异常:

MySQLIntegrityConstraintViolationException:无法添加或更新子行:外键约束失败(testdb.child,CONSTRAINT parent_fk FOREIGN KEY(parent_id)REFERENCES parent(id)ON DELETE NO ACTION ON UPDATE NO ACTION)

我假设这是因为在尝试插入子项时未完全插入父项.

如果我不将子项添加到父项,则父项插入就好了.我尝试切换GeneratedValue生成策略,但这没有帮助.

任何想法如何同时插入父母和孩子?

编辑:即使我先坚持父母,我仍然会得到同样的错误.我确定这是因为没有在孩子中设置parent_id; 子进程是默认构造的,因此parent_id被设置为0,因此不存在外键约束验证.

有没有办法让jpa自动设置分配给父实例的子节点的parent_id?

Nat*_*mer 13

你的关系不一定是双向的.这里的评论中有一些错误的信息.

您还说过,您已将字段"parentId"添加到Child实体中,因为您认为JPA需要"知道"父字段,以便它可以设置该值.问题不在于JPA根据您提供的注释而不了解该字段.问题是您提供了有关该字段的"太多"信息,但该信息并非内部一致.

将Parent中的字段和注释更改为:

@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
@JoinColumn(name = "parent_id")
private List<Child> children;
Run Code Online (Sandbox Code Playgroud)

然后完全从Child实体中删除"parentId".您之前已指定了JoinTable注释.但是,你想要的不是JoinTable.JoinTable将创建另外的第三个表,以便将两个实体相互关联.你想要的只是一个JoinColumn.将JoinColumn批注添加到也使用OneToMany注释的字段后,您的JPA实现将知道您正在将CHK添加到CHILD表中.问题是JPA已经使用列parent_id定义了CHILD表.

想一想,你给它两个CHILD表函数和parent_id列的冲突定义.在一种情况下,您告诉JPA它是一个实体,而parent_id只是该实体中的一个值.另一方面,您告诉JPA您的CHILD表不是实体,而是用于在CHILD和PARENT表之间创建外键关系.问题是您的CHILD表已经存在.然后,当你持久化你的实体时,你告诉它parent_id显式为null(未设置)但是你还告诉它你的parent_id应该被更新以设置对父表的外键引用.

我使用上面描述的更改修改了代码,我也称为"persist"而不是"merge".

这导致了3个SQL查询

insert into PARENT (ID) values (default)
insert into CHILD (ID) values (default)
update CHILD set parent_id=? where ID=?
Run Code Online (Sandbox Code Playgroud)

这完全反映了你想要的东西.PARENT条目已创建.创建CHILD条目,然后更新CHILD记录以正确设置外键.

如果您改为添加注释

@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
@JoinColumn(name = "parent_id", nullable = false)
private List<Child> children;
Run Code Online (Sandbox Code Playgroud)

然后它将在插入子项时运行以下查询

insert into CHILD (ID, parent_id) values (default, ?)
Run Code Online (Sandbox Code Playgroud)

从一开始就设置你的FK属性.