简体   繁体   English

保存单向一对多映射时,Hibernate 在外键字段中插入 null 值

[英]Hibernate is inserting null value in foreign key field when saving a unidirectional one to many mapping

I have a unidirectional one to many relationship.我有一个单向的一对多关系。 The one side is PARENT, the many side is CHILD.一方面是父母,多方面是孩子。 For one PARENT there can be many CHILD.一个家长可以有多个孩子。 But for a CHILD there is exactly one PARENT.但是对于一个孩子来说,只有一个父母。 On the Java side the relation is unidirectional, I need to access the CHILDS of a PARENT, but I don't want to store the PARENT for CHILDS.在 Java 方面,关系是单向的,我需要访问父母的孩子,但我不想为孩子存储父母。 So these are the objects:所以这些是对象:

Parent:家长:

@Entity
public class Parent {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    
    private String job;
    
    @OneToMany(targetEntity = Child.class, fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinColumn(name = "PARENT_ID")
    private Set<Child> childs;

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getJob() { return job; }
    public void setJob(String job) { this.job = job; }

    public Set<Child> getChilds() {
        if(childs != null) { return childs; }
        else {
            childs = new HashSet<Child>();
            return childs;
        }
    }
    public void setChilds(Set<Child> childs) { this.childs = childs; }

}

Child:孩子:

@Entity
public class Child {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String hobby;

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getHobby() { return hobby; }
    public void setHobby(String hobby) { this.hobby = hobby; }

}

This is the code which creates a child, a parent with that child and then saves parent:这是创建一个孩子,该孩子的父母,然后保存父母的代码:

@Test
public void test() {
    Child c = new Child();
    c.setHobby("hobby");
    
    Parent p = new Parent();
    p.setJob("test");
    p.getChilds().add(c);
    
    parentRepository.save(p);
}

Then when I run the code there is an error because Hibernate does not set the PARENT_ID on CHILD when inserting it.然后当我运行代码时出现错误,因为 Hibernate 在插入时没有在 CHILD 上设置 PARENT_ID。 In the log it is clear that Hibernate retrieved the two ids needed from the sequence generator YET it leaves CHILD.PARENT_ID null:在日志中,很明显 Hibernate 从序列生成器中检索了所需的两个 id,但它留下了 CHILD.PARENT_ID null:

2020-07-28 13:21:00.689  INFO 16295 --- [           main] jpa_hibernate_spring_boot.MyTest         : Starting MyTest on riskop-ESPRIMO-P556 with PID 16295 (started by riskop in /home/riskop/Documents/privat/java/jpa_hibernate_spring_boot)
2020-07-28 13:21:00.690  INFO 16295 --- [           main] jpa_hibernate_spring_boot.MyTest         : No active profile set, falling back to default profiles: default
2020-07-28 13:21:00.950  INFO 16295 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFERRED mode.
2020-07-28 13:21:00.988  INFO 16295 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 32ms. Found 1 JPA repository interfaces.
2020-07-28 13:21:01.362  INFO 16295 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2020-07-28 13:21:01.491  INFO 16295 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2020-07-28 13:21:01.608  INFO 16295 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2020-07-28 13:21:01.660  INFO 16295 --- [         task-1] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2020-07-28 13:21:01.703  INFO 16295 --- [         task-1] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 5.4.18.Final
2020-07-28 13:21:01.743  INFO 16295 --- [           main] DeferredRepositoryInitializationListener : Triggering deferred initialization of Spring Data repositories…
2020-07-28 13:21:01.820  INFO 16295 --- [         task-1] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.1.0.Final}
2020-07-28 13:21:01.977  INFO 16295 --- [         task-1] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
2020-07-28 13:21:02.388  INFO 16295 --- [         task-1] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2020-07-28 13:21:02.393  INFO 16295 --- [         task-1] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2020-07-28 13:21:02.521  INFO 16295 --- [           main] DeferredRepositoryInitializationListener : Spring Data repositories initialized!
2020-07-28 13:21:02.526  INFO 16295 --- [           main] jpa_hibernate_spring_boot.MyTest         : Started MyTest in 1.983 seconds (JVM running for 2.575)
2020-07-28 13:21:02.578 DEBUG 16295 --- [           main] org.hibernate.SQL                        : 
    call next value for hibernate_sequence
2020-07-28 13:21:02.595 DEBUG 16295 --- [           main] org.hibernate.SQL                        : 
    call next value for hibernate_sequence
2020-07-28 13:21:02.601 DEBUG 16295 --- [           main] org.hibernate.SQL                        : 
    insert 
    into
        parent
        (job, id) 
    values
        (?, ?)
2020-07-28 13:21:02.603 TRACE 16295 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [test]
2020-07-28 13:21:02.604 TRACE 16295 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [BIGINT] - [1]
2020-07-28 13:21:02.605 DEBUG 16295 --- [           main] org.hibernate.SQL                        : 
    insert 
    into
        child
        (hobby, id) 
    values
        (?, ?)
2020-07-28 13:21:02.606 TRACE 16295 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [hobby]
2020-07-28 13:21:02.606 TRACE 16295 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [BIGINT] - [2]
2020-07-28 13:21:02.607  WARN 16295 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 23502, SQLState: 23502
2020-07-28 13:21:02.607 ERROR 16295 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : NULL not allowed for column "PARENT_ID"; SQL statement:
insert into child (hobby, id) values (?, ?) [23502-200]
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 2.105 s <<< FAILURE! - in jpa_hibernate_spring_boot.MyTest
[ERROR] test  Time elapsed: 0.089 s  <<< ERROR!
org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
        at jpa_hibernate_spring_boot.MyTest.test(MyTest.java:33)
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
        at jpa_hibernate_spring_boot.MyTest.test(MyTest.java:33)
Caused by: org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: 
NULL not allowed for column "PARENT_ID"; SQL statement:
insert into child (hobby, id) values (?, ?) [23502-200]
        at jpa_hibernate_spring_boot.MyTest.test(MyTest.java:33)

How should I fix it?我应该如何解决它?

Note that if I remove the not null constraint from CHILD.PARENT_ID, then the code works.请注意,如果我从 CHILD.PARENT_ID 中删除 not null 约束,则代码有效。 But I obviously need that check.但我显然需要那张支票。

The whole code is here:整个代码在这里:

https://github.com/riskop/jpa_hibernate_problem_parent_id_is_not_filled_by_hibernate https://github.com/riskop/jpa_hibernate_problem_parent_id_is_not_filled_by_hibernate


Thank you jwpol for the "nullable = false" info: If I apply that to Parent:感谢 jwpol 提供“nullable = false”信息:如果我将其应用于父级:

    @JoinColumn(name = "PARENT_ID", nullable = false)
    private Set<Child> childs;

Then it starts working!然后它开始工作!

Yet I am curious why Hibernate does not do this by default and why does it try to update PARENT_ID even if "nullable = false" is given:然而我很好奇为什么 Hibernate 默认不这样做,为什么即使给出“nullable = false”它也会尝试更新 PARENT_ID:

2020-07-28 14:16:02.161  INFO 20458 --- [           main] jpa_hibernate_spring_boot.MyTest         : Started MyTest in 2.007 seconds (JVM running for 2.599)
2020-07-28 14:16:02.220 DEBUG 20458 --- [           main] org.hibernate.SQL                        : 
    call next value for hibernate_sequence
2020-07-28 14:16:02.242 DEBUG 20458 --- [           main] org.hibernate.SQL                        : 
    call next value for hibernate_sequence
2020-07-28 14:16:02.250 DEBUG 20458 --- [           main] org.hibernate.SQL                        : 
    insert 
    into
        parent
        (job, id) 
    values
        (?, ?)
2020-07-28 14:16:02.252 TRACE 20458 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [test]
2020-07-28 14:16:02.253 TRACE 20458 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [BIGINT] - [1]
2020-07-28 14:16:02.255 DEBUG 20458 --- [           main] org.hibernate.SQL                        : 
    insert 
    into
        child
        (hobby, parent_id, id) 
    values
        (?, ?, ?)
2020-07-28 14:16:02.255 TRACE 20458 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [hobby]
2020-07-28 14:16:02.255 TRACE 20458 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [BIGINT] - [1]
2020-07-28 14:16:02.256 TRACE 20458 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [BIGINT] - [2]
2020-07-28 14:16:02.260 DEBUG 20458 --- [           main] org.hibernate.SQL                        : 
    update
        child 
    set
        parent_id=? 
    where
        id=?
2020-07-28 14:16:02.261 TRACE 20458 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [BIGINT] - [1]
2020-07-28 14:16:02.261 TRACE 20458 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [BIGINT] - [2]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.144 s - in jpa_hibernate_spring_boot.MyTest

Do you have any idea why this seemingly unneccessary update happens?您知道为什么会发生这种看似不必要的更新吗?

Use @JoinColumn(name = "PARENT_ID", nullable = false) , you will tell hibernate that there is not null constraint.使用@JoinColumn(name = "PARENT_ID", nullable = false) ,你会告诉 hibernate 没有 null 约束。

Vlad Mihalcea has a good article on exactly what your are doing. Vlad Mihalcea 有一篇关于你在做什么的好文章。 It can be found at https://vladmihalcea.com/the-best-way-to-map-a-onetomany-association-with-jpa-and-hibernate .它可以在https://vladmihalcea.com/the-best-way-to-map-a-onetomany-association-with-jpa-and-hibernate找到。

Basically when you provide the @JoinColumn annotation Hibernate will perform a persist on the parent first, will persist the children minus foreign key second, and then update the child foreign keys with the parent's primary key.基本上,当您提供@JoinColumn 注释时,Hibernate 将首先在父级上执行持久化,然后将子级减去外键持久化,然后使用父级的主键更新子外键。 This follows Hibernate's flush order.这遵循 Hibernate 的刷新顺序。 To prevent the additional update his suggestion is to make the association bi-directional and manage the association bidirectionally via helper methods on the parent.为了防止额外的更新,他的建议是使关联双向并通过父级上的辅助方法双向管理关联。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM