【问题标题】:JPA insert parent/child results in MySQLIntegrityConstraintViolationExceptionJPA 插入父/子导致 MySQLIntegrityConstraintViolationException
【发布时间】:2015-11-12 21:38:19
【问题描述】:

这个问题已经被问过很多次了,但是我没有找到任何好的答案,所以我会再问一次。

我的父子单向关系如下:

@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
}

我创建一个父实例,为其分配一个子列表并尝试将其持久化:

Parent p = new Parent();
List<Child> children = new ArrayList<Child>();
Child c = new Child();
children.add(c);
p.addChildren(children);
em.merge(p);

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

MySQLIntegrityConstraintViolationException:无法添加或更新 子行:外键约束失败 (testdb.child,约束parent_fk 外键 (parent_id) 引用 parent (id) 开 更新时删除无操作无操作)

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

如果我不将子项添加到父项,则父项会很好地插入。 我尝试切换 GeneratedValue 生成策略,但没有帮助。

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

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

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

【问题讨论】:

  • 如果这应该是单向关系,那么你为什么将parentId 放入Child 实体中?外键存储在连接表中。
  • @ventsyv - 总结得恰到好处,孩子方面根本不需要那个id。
  • @Biraj Choudhury 我在 POJO 中不需要它,但我在表中确实需要它,所以我认为 JPA 需要“了解”它,以便将其设置为正确的价值。如果我将其从 POJO 中删除,则为插入子项而生成的查询未设置 parent_id 字段,因此将不起作用。
  • 你说你需要一个单向关系,这意味着你有三个表 parent(parent Id - PK , ChildId- FK, 其他字段), child (Child Id - PK), parent_child ( Child Id - Parent一对多关系的 ID)。只有当您需要双向关系并且您可以通过@ManyToOne Parent parent 来实现时,您才需要 child 中的 parent id;除了这两种情况,你能告诉你想要实现什么吗?
  • @Biraj Choudhury 我有 2 张桌子 - 父母和孩子,其中孩子有 parent_id 标识父母。这是一对多的关系,一个父母可以有多个孩子,但每个孩子只有一个父母。我想归档(写入数据库)一个父对象列表,其中每个父对象都包含一个子对象列表。

标签: java mysql hibernate jpa


【解决方案1】:

您的关系不必是双向的。这里的 cmets 中有一些错误信息。

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

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

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

然后从子实体中完全删除“parentId”。 您之前已经指定了一个 JoinTable 注释。但是,您想要的不是 JoinTable。 JoinTable 将创建一个额外的第三个表,以便将两个实体相互关联。你想要的只是一个 JoinColumn。将 JoinColumn 注释添加到也使用 OneToMany 注释的字段后,您的 JPA 实现将知道您正在将 FK 添加到 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=?

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

如果您改为添加注释

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

然后它会在插入孩子的时候运行下面的查询

insert into CHILD (ID, parent_id) values (default, ?)

因此从一开始就正确设置您的 FK。

【讨论】:

  • 感谢您的回答,这正是我最终所做的。很好的解释。有没有办法在子插入查询中设置 parent_id 字段,而无需运行单独的更新查询?
  • 我刚刚更新了我的答案,将注释修改为@JoinColumn(name = "parent_id", nullable = false)
  • 我的 JPA 实现仍然运行第二个更新查询以再次设置 FK(不知道为什么),但我还在我的 SQL 日志中看到 FK 已正确设置为从原始插入开始跨度>
  • 这里一样,更新查询甚至在 nullable 设置为 false 的情况下运行,这很奇怪......
  • 完美答案。事实证明,因为我错过了nullable=false,所以我把头撞到了墙上。谢谢!
【解决方案2】:

向父实体添加 updatable=false 解决了在子表上执行插入和更新的问题。但是,我不知道为什么会这样,事实上,我认为我所做的并不正确,因为这意味着如果我必须在以后更新子表。

【讨论】:

    【解决方案3】:

    我知道使用em.persists(...) 来坚持一个有孩子的新父母对我有用。

    使用em.merge(...),我真的不知道,但听起来它应该可以工作,但显然你遇到了麻烦,因为你的 JPA 实现试图在父级之前保留子级。

    也许检查一下这是否适合你:https://vnageswararao.wordpress.com/2011/10/06/persist-entities-with-parent-child-relationship-using-jpa/

    我不知道这是否在您的问题中起作用,但请记住,em.merge(p); 将返回一个托管实体...而p 将保持非托管状态,您的孩子将链接到 @ 987654326@。

    A) 试试em.persists(...) 而不是em.merge(...)

    如果你不能

    B) 您正在合并 parent... 而您的 cascade 设置为 CascadeType.PERSIST。尝试将其更改为

    • cascade=CascadeType.ALL
    • cascade={CascadeType.PERSIST, CascadeType.MERGE}

    我知道merge 将保留新创建的实体,并且应该像persists 一样行事,但这些是我最好的提示。

    【讨论】:

    • 查看更新后的问题,我想我确定这不是级联的问题。
    • 我尝试了所有这些但没有任何效果。我认为问题在于 JPA 在尝试插入子对象时无法弄清楚父 ID 是什么。
    【解决方案4】:

    你想达到什么,你可以用这段代码来实现。

        @Entity
        @Table(name = "PARENT")
        public class Parent {
            @Id
            @GeneratedValue(strategy = GenerationType.IDENTITY)
            @Column(name = "ID")
            private Long parentId;
    
            @OneToMany(cascade = CascadeType.PERSIST)
            private List<Child> children;
        }
    
        @Entity
        @Table(name = "CHILD")
        public class Child {
            @Id
            @GeneratedValue(strategy = GenerationType.IDENTITY)
            @Column(name = "ID")
            private Long id;
    
            @ManyToOne
            Parent parent;
        }
    

    【讨论】:

    • 所以它必须是双向的?这意味着子 POJO 需要有父 POJO 的实例?我试图在子 POJO 中没有父级,因为它没有必要(不会被使用)。
    • 这就是你想要的,在孩子中有父 ID
    • 生成的表只有id,不会有Child中父的其他字段。
    • 我说的是java对象。 Child java 对象会有对 Parent java 对象的引用?
    • 是的,它会的。你为什么要把它放在桌子上?如果您不想在代码中使用它?
    猜你喜欢
    • 2019-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-13
    • 2015-06-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多