我正在使用@OneToMany批注来保存父实体和子实体,但是在特定情况下保存子实体时遇到了问题。
子实体在两种情况下被保存:
但是,当父项与子项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;
}
在mapper类中,我显式设置了父表的主键。
Oracle数据库方面我在下面添加了外键约束
ALTER TABLE child_table
ADD CONSTRAINT FK_parent_table
FOREIGN KEY (REFERENCE_ID)
REFERENCES Parent_table(REFERENCE_ID);
我已经浏览了关于stackoverflow的类似问题,这表明如果您使用现有列作为外键,则该列的现有值不应为null。但是在我的情况下,“ REFERENCE_ID”列已经不可为空。
请让我知道或建议我是否需要添加其他内容以使其正常工作。
谢谢。
编辑:在更新方案中,Hibernate生成以下查询:
update child_table set reference_id=null where reference_id=? and child_seq=?
其中reference_id是Parent的主键,child_seq是Child的主键
任何知道为什么冬眠试图更新父母的主键我在孩子的实体中明确设置了父主键的值
这里实际上存在三个问题:
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关系
建模的规范方法如下:
ParentEntity
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
@JoinColumn(name = "REFERENCE_ID", nullable = false)
private Set<ChildEntity> childrens;
ChildEntity
@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版本)。
ParentEntity
@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。龙目岛生成的等于和hashCode
这与您的问题没有直接关系,但我认为您迟早会遇到此问题。您正在使用@Data批注(我假设它们是Lombok的批注,如果不是,请忽略本节)。默认情况下,它们将从所有字段(包括id)生成equals和hashCode方法。可以在ParentEntity(手动设置ID)中很好。但是,在由数据库生成ID(又称asSeq
)的ChildEntity中,它破坏了hashCode()/ equals()契约。这可能会导致真正的偷偷摸摸的错误。从Hibernate文档中:
这里的问题是生成的标识符的使用,Set的约定和equals / hashCode实现之间的冲突。 Set表示当对象是Set的一部分时,对象的equals / hashCode值不应更改。但这正是此处发生的情况,因为equals / hasCode基于(生成的)ID,在提交JPA事务之前,该ID才设置。
您可能想在这里阅读更多信息: