Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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. 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. In the log it is clear that Hibernate retrieved the two ids needed from the sequence generator YET it leaves 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. 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


Thank you jwpol for the "nullable = false" info! If I apply that to Parent:

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

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?

like image 793
riskop Avatar asked Mar 03 '23 05:03

riskop


2 Answers

Vlad Mihalcea has a good article on exactly what your are doing. It can be found at 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. This follows Hibernate's flush order. 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.

like image 197
Lee Greiner Avatar answered Mar 05 '23 04:03

Lee Greiner


Use @JoinColumn(name = "PARENT_ID", nullable = false), you will tell hibernate that there is not null constraint.

like image 41
jwpol Avatar answered Mar 05 '23 04:03

jwpol