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?
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.
Use
@JoinColumn(name = "PARENT_ID", nullable = false)
, you will tell hibernate that there is not null constraint.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With