The @OneToOne association can either be unidirectional or bidirectional. A unidirectional association follows the relational database foreign key semantics, the client-side owning the relationship. A bidirectional association features a mappedBy @OneToOne parent side too.

Unidirectional @OneToOne

package org.example.demo.hibernate;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Persistence;

public class Test {
    public static void main(String[] args) throws Exception {
        EntityManagerFactory factory = Persistence.createEntityManagerFactory("test");
        EntityManager em = factory.createEntityManager();

        Phone phone = new Phone();
        phone.number = "123-456-7890";

        PhoneDetails details = new PhoneDetails();
        details.provider = "Nokia";

        phone.details = details;

        em.getTransaction().begin();
        em.persist(phone);
        em.persist(details);
        em.getTransaction().commit();

        em.close();
        factory.close();
    }

    @Entity(name = "Phone")
    public static class Phone {
        @Id
        @GeneratedValue
        private Long id;

        @Column(name = "`number`")
        private String number;

        @OneToOne
        @JoinColumn(name = "details_id")
        private PhoneDetails details;
    }

    @Entity(name = "PhoneDetails")
    public static class PhoneDetails {
        @Id
        @GeneratedValue
        private Long id;
        String provider;
    }
}

生成的 SQL

alter table Phone drop constraint FKnoj7cj83ppfqbnvqqa5kolub7
drop table if exists Phone cascade
drop table if exists PhoneDetails cascade
drop sequence if exists hibernate_sequence

create sequence hibernate_sequence start 1 increment 1
create table Phone (id int8 not null, "number" varchar(255), details_id int8, primary key (id))
create table PhoneDetails (id int8 not null, provider varchar(255), primary key (id))
alter table Phone add constraint FKnoj7cj83ppfqbnvqqa5kolub7 foreign key (details_id) references PhoneDetails
select nextval ('hibernate_sequence')
select nextval ('hibernate_sequence')
insert into Phone (details_id, "number", id) values (NULL, '123-456-7890', 1)
insert into PhoneDetails (provider, id) values ('Nokia', 2)
update Phone set details_id=2, "number"='123-456-7890' where id=1

注意,先是 insert Phone 的 details_id=null 然后再 update set details_id=2

Bidirectional @OneToOne

package org.example.demo.hibernate;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Persistence;

public class Test {
    public static void main(String[] args) throws Exception {
        EntityManagerFactory factory = Persistence.createEntityManagerFactory("test");
        EntityManager em = factory.createEntityManager();

        Phone phone = new Phone();
        phone.number = "123-456-7890";

        PhoneDetails details = new PhoneDetails();
        details.provider = "Nokia";

        phone.details = details;
        details.phone = phone;

        em.getTransaction().begin();
        em.persist(phone);
        em.flush();
        details.phone = null;
        phone.details = null;
        em.getTransaction().commit();

        em.close();
        factory.close();
    }

    @Entity(name = "Phone")
    public static class Phone {
        @Id
        @GeneratedValue
        private Long id;

        @Column(name = "`number`")
        private String number;

        @OneToOne(mappedBy = "phone", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
        private PhoneDetails details;
    }

    @Entity(name = "PhoneDetails")
    public static class PhoneDetails {
        @Id
        @GeneratedValue
        private Long id;
        String provider;

        @OneToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "phone_id")
        private Phone phone;
    }
}

生成 SQL

alter table PhoneDetails drop constraint FKeotuev8ja8v0sdh29dynqj05p
drop table if exists Phone cascade
drop table if exists PhoneDetails cascade
drop sequence if exists hibernate_sequence

create sequence hibernate_sequence start 1 increment 1
create table Phone (id int8 not null, "number" varchar(255), primary key (id))
create table PhoneDetails (id int8 not null, provider varchar(255), phone_id int8, primary key (id))
alter table PhoneDetails add constraint FKeotuev8ja8v0sdh29dynqj05p foreign key (phone_id) references Phone
select nextval ('hibernate_sequence')
select nextval ('hibernate_sequence')
insert into Phone ("number", id) values ('123-456-7890', 1)
insert into PhoneDetails (phone_id, provider, id) values (1, 'Nokia', 2)
delete from PhoneDetails where id=2

When using a bidirectional @OneToOne association, Hibernate enforces the unique constraint upon fetching the child-side.

package org.example.demo.hibernate;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Persistence;

public class Test {
    public static void main(String[] args) throws Exception {
        EntityManagerFactory factory = Persistence.createEntityManagerFactory("test");
        EntityManager em = factory.createEntityManager();

        Phone phone = new Phone();
        phone.number = "123-456-7890";

        PhoneDetails details = new PhoneDetails();
        details.provider = "Nokia";

        PhoneDetails otherDetails = new PhoneDetails();
        otherDetails.provider = "Google";

        phone.details = details;
        details.phone = phone;

        otherDetails.phone = phone;

        em.getTransaction().begin();
        em.persist(phone);
        em.persist(otherDetails);
        em.flush();
        em.getTransaction().commit();

        em.clear();

        // org.hibernate.HibernateException: More than one row with the given identifier was found: 1, for class: org.example.demo.hibernate.Test$PhoneDetails
        em.find(Phone.class, phone.id);

        em.close();
        factory.close();
    }

    @Entity(name = "Phone")
    public static class Phone {
        @Id
        @GeneratedValue
        private Long id;

        @Column(name = "`number`")
        private String number;

        @OneToOne(mappedBy = "phone", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
        private PhoneDetails details;
    }

    @Entity(name = "PhoneDetails")
    public static class PhoneDetails {
        @Id
        @GeneratedValue
        private Long id;
        String provider;

        @OneToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "phone_id")
        private Phone phone;
    }
}

注意, persist 时是不管的,只有在 unique 时才会检查 unique 。因为 persist 时 PhoneDetails 端才 own relationship ,而 Phone 端其实是 inverse 端。

results matching ""

    No results matching ""