JPA插入父/子会导致MySQLIntegrityConstraintViolationException
这已经被问过很多次了,但是我没有找到任何好的答案,所以我会再问一次。
我有父子关系单向关系如下:
@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 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 children = new ArrayList(); Child c = new Child(); children.add(c); p.addChildren(children); em.merge(p);
代码运行时,我得到以下exception:
MySQLIntegrityConstraintViolationException:无法添加或更新子行:外键约束失败(
testdb
。child
,CONSTRAINTparent_fk
FOREIGN KEY(parent_id
)REFERENCESparent
(id
)ON DELETE NO ACTION ON UPDATE NO ACTION)
我假设这是因为在尝试插入子项时未完全插入父项。
如果我不将子项添加到父项,则父项插入就好了。 我尝试切换GeneratedValue生成策略,但这没有帮助。
任何想法如何同时插入父母和孩子?
编辑:即使我先坚持父母,我仍然会得到同样的错误。 我确定这是因为parent_id没有设置在孩子身上; 子节点是默认构造的,因此parent_id设置为0,因此不存在外键约束validation。
有没有办法让jpa自动设置分配给父实例的子节点的parent_id?
你的关系不一定是双向的。 这里的评论中有一些错误的信息。
您还说过,您已将字段“parentId”添加到Child实体中,因为您认为JPA需要“知道”父字段,以便它可以设置该值。 问题不在于JPA根据您提供的注释而不了解该字段。 问题是您提供了有关该字段的“太多”信息,但该信息并非内部一致。
将Parent中的字段和注释更改为:
@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST) @JoinColumn(name = "parent_id") private List children;
然后完全从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=?
这完全反映了你想要的东西。 PARENT条目已创建。 创建CHILD条目,然后更新CHILD记录以正确设置外键。
如果您改为添加注释
@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST) @JoinColumn(name = "parent_id", nullable = false) private List children;
然后它会在插入子项时运行以下查询
insert into CHILD (ID, parent_id) values (default, ?)
从一开始就设置你的FK属性。
将updatable = false添加到父实体解决了在子表上执行插入和更新的问题。 但是,我不知道为什么会这样,事实上,我不认为我在做什么是正确的,因为这意味着如果必须的话我以后不能更新子表。
我知道坚持一个有孩子的新父母为我工作使用em.persists(...)
。
使用em.merge(...)
,我真的不知道,但它听起来应该可以工作,但显然你遇到了麻烦,因为你的JPA实现试图在父母之前坚持孩子。
也许检查一下这是否适合你: https : //vnageswararao.wordpress.com/2011/10/06/persist-entities-with-parent-child-relationship-using-jpa/
我不知道这是否em.merge(p);
你的问题起作用,但要记住em.merge(p);
将返回一个托管实体…并且p
将保持未管理状态,并且您的孩子将与p
链接。
A)尝试em.persists(...)
而不是em.merge(...)
如果你不能
B)您正在合并parent
…并且您cascade
设置为CascadeType.PERSIST
。 尝试将其更改为
-
cascade=CascadeType.ALL
要么 -
cascade={CascadeType.PERSIST, CascadeType.MERGE}
我知道merge
将持久保存新创建的实体,并且应该表现为persists
,但这些是我最好的提示。
您希望通过此代码实现的目标。
@Entity @Table(name = "PARENT") public class Parent { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "ID") private Long parentId; @OneToMany(cascade = CascadeType.PERSIST) private List children; } @Entity @Table(name = "CHILD") public class Child { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "ID") private Long id; @ManyToOne Parent parent; }
- Spring Data JPA – “无法初始化代理 – 无会话” – 使用标记为事务的方法
- MySQL 5.5.9和TYPE上的Hibernate表创建错误
- 寻找一个不被弃用的会话工厂
- 从数据库加载26MB文本数据消耗了258MB的JVM堆
- 如何使用Hibernate / JPA注释覆盖GenerationType策略?
- 在persistence.xml中引用Tomcat JNDI数据源
- 使用SequenceGenerator的javax.persistence.EntityExistsException
- org.hibernate.TransientObjectException:object引用未保存的瞬态实例 – 在刷新之前保存瞬态实例
- 处理遗留数据库时,Hibernate会对丢失的行进行扼流