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 端。