Search code examples
javahibernateormone-to-onebidirectional

OneToOne bidirectional mapping foreign key auto fill


I have a relationship one-to-one between two tables, but the foreign key is on the one I don't need to map, the DBA did this in favor of future changes.

Let's imagine we have User and Address, today every user has only one address, and it will be mapped this way, but DBA believe in the future it could be a one to many mapping, so the foreign key of user is on the address, but the application have instances of users, which is important to fetch address automatically.

We did it right, as follow:

@Entity
@Table(name = "user")
class User {
    @Id
    @Column(name = "user_id")
    private Long id;

    //...

    @OneToOne(cascade = CascadeType.MERGE, mappedBy = "user")
    private Address address; // this attribute is crucial
}

@Entity
@Table(name = "address")
class Address {
    @Id
    @Column(name = "address_id")
    private Long id;

    @OneToOne
    @JoinColumn(name = "user_id")
    private User user; // this attribute is not needed for the business at all, but mappedBy requires it

    //...

}

Database:

-- SQL:
CREATE TABLE user
(
    user_id INT NOT NULL,
    -- ...
    CONSTRAINT user_pk PRIMARY KEY (user_id)
);

CREATE TABLE address
(
    address_id INT NOT NULL,
    user_id INT NOT NULL,
    -- ...
    CONSTRAINT address_pk PRIMARY KEY (address_id),
    CONSTRAINT address_user_id_fk FOREIGN KEY (user_id) REFERENCES user (user_id),
    CONSTRAINT address_user_id_uk UNIQUE (user_id) -- it says it's a one to one relation for now, if in the future it won't be anymore, just remove this constraint
);

The problem is when save a instance of user with a new instance of address, the user's attribute of address is null, so I was expecting Hibernate was smart enough to set it the value from the user's instance it comes from.

I'd been looking for a solution for a couple of days, but still didn't find how to solve this, meanwhile I'm setting the value manually, but I expect I don't need to do so.


Solution

  • The standard solution is to properly update both sides of the bidirectional association (although only the owning side needs to be updated for the association to be saved to the database). Add to the Address setter in the User class:

    public void setAddress(Address address) {
       this.address = address;
       address.setUser(this);
    }
    

    Also, you may want to extend cascading options for the address property to include PERSIST as well, so that it is always persisted together with its user:

     @OneToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, mappedBy = "user")
     private Address address;
    

    Then you can set an address to a user and persist both:

    user.setAddress(address);
    session.persist(user);