I'm learning Hibernate (v. 6.6.1 final) and have a problem with a OneToOne relation
@Entity
@Table(name = "customer")
public class Customer {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@Column(name = "name")
private String name;
@Column(name = "age")
private int age;
@OneToOne(mappedBy = "customer", cascade = CascadeType.PERSIST)
private Passport passport;
// constructors, getters and setters
}
@Entity
@Table(name = "passport")
public class Passport {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@Column(name = "passport_number")
private String passportNumber;
@OneToOne
@JoinColumn(name = "customer_id", referencedColumnName = "id")
private Customer customer;
//constructors, getters and setters
}
try {
session.beginTransaction();
Customer customer = new Customer("Customer 1", 77);
Passport passport = new Passport("777");
customer.setPassport(passport);
session.persist(customer);
session.getTransaction().commit();
}
finally {
factory.close();
}
And after this in database saves:
Customer {id: 1, name: "Customer 1", age: 77}
and
Passport {id: 1, passport_number: "777", customer_id: null}
Why doesn't hibernate save customer_id if I configure relation ?
If I bind from both sides then everything is ok
try {
session.beginTransaction();
Customer customer = new Customer("Customer 1", 77);
Passport passport = new Passport("777");
passport.setCustomer(customer);
customer.setPassport(passport);
session.persist(customer);
session.getTransaction().commit();
}
finally {
factory.close();
}
Why must I manually set relation to every object if I configure relation? Why hibernate is not saving customer_id but save Passport object ?
If I bind the relation from both sides, it works, but why doesn't hibernate automatically save my fk key ?
When dialing with jpa relationships, you have to consider which side is the owner of the relational id. In this case, you have the Passport
entity that maps the customer_id
column, so this is the owner, not the Customer
.
Because of this, saving should start from the owner and then propagate to the other related entities. First, add the cascade to the Passport
's customer
attribute:
@OneToOne(cascade = CascadeType.PERSIST)
@JoinColumn(name = "customer_id", referencedColumnName = "id")
private Customer customer;
Then try your test like this:
passport.setCustomer(customer);
session.persist(passport);
This will first save the Customer
instance, getting its id
, that will be set into the Passport
instace, thus saving all this data in the following insert
statement.
To be sure that the customer
is set before persisting the Passport
instance into the db, you can also enforce the @JoinColumn
making it not nullable:
@OneToOne(cascade = CascadeType.PERSIST)
@JoinColumn(name = "customer_id", referencedColumnName = "id", nullable = false)
private Customer customer;
This will lead to an exception being thrown if this attribute is not set while persisting.
A caveat: please use Integer
(or, in general, a wrapped type like Long
) for the id
s, instead of the primitive types int
, long
, etc. following the recommendations of JPA (see also this Should I use Primitives or wrappers in JPA2.0?).