javahibernatehibernate-mappinghibernate-6.x

Hibernate OneToOne association, owning side not saving foreing key


I'm learning Hibernate (v. 6.6.1 final) and have a problem with a OneToOne relation

  1. Customer Entity
@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
}
  1. Passport Entity
@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
}
  1. I configure OneToOne relation
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 ?


Solution

  • 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 ids, 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?).