前几节我们介绍了一对多的关系,今天我们学习一对一关系以及这种映射方式的最佳实践,先上业务实例图。
从图中可以看出,研究所和社交账号有一对一的关系,部门和社交账号也有一对一的关系,我们选用研究所和社交账号的关系来说明问题。
Bidirectional @OneToOne
研究所实体
mport javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
@Entity(name = "Institute")
@Table(name = "institutes")
public class Institute implements Serializable {
@Id
@GeneratedValue
private Long id;
@OneToMany(
mappedBy = "institute",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private Set<Department> departments = new HashSet<>(0);
@OneToOne( mappedBy = "institute", cascade = CascadeType.ALL,
fetch = FetchType.LAZY, optional = false)
private SocialProfile socialProfile;
private String name;
public Institute(){}
public Institute(String name) {
this.name = name;
}
public void setSocialProfile(SocialProfile socialProfile) {
if (null == socialProfile){
if (this.socialProfile!=null) {
this.socialProfile.setInstitute(null);
}
} else {
socialProfile.setInstitute(this);
}
this.socialProfile = socialProfile;
}
public void addDepartment(Department department){
this.departments.add(department);
department.setInstitute(this);
}
public void removeDepartment(Department department) {
this.departments.remove(department);
department.setInstitute(null);
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Set<Department> getDepartments() {
return departments;
}
public void setDepartments(Set<Department> departments) {
this.departments = departments;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
这里增加了一个社交账号的属性,采用延迟加载策略。自定义了一个setSocialProfile()的方法。
社交账号实体
import javax.persistence.*;
import java.io.Serializable;
@Entity( name = "SocialProfile")
@Table(name = "socialprofiles")
public class SocialProfile implements Serializable {
@Id
@GeneratedValue
private long id;
private String shortName;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn()
private Institute institute;
public SocialProfile(){}
public SocialProfile(String shortName) {
this.shortName = shortName;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getShortName() {
return shortName;
}
public void setShortName(String shortName) {
this.shortName = shortName;
}
public Institute getInstitute() {
return institute;
}
public void setInstitute(Institute institute) {
this.institute = institute;
}
}
测试代码:
@Test
public void testOneToOneSave() {
Institute institute = new Institute("深圳研究所");
institute.setSocialProfile(new SocialProfile("深圳研究所-社交账号"));
InstituteDAO dao = new InstituteDAO();
dao.save(institute);
}
日志信息:
Hibernate:
create table institutes (
id bigint not null,
name varchar(255),
primary key (id)
)
Hibernate:
create table socialprofiles (
id bigint not null,
shortName varchar(255),
institute_id bigint,
primary key (id)
)
Hibernate: create sequence hibernate_sequence start with 1 increment by 1
Hibernate:
alter table socialprofiles
add constraint FK8o0yil50tmsnyfbqgrlj7b5v0
foreign key (institute_id)
references institutes
Hibernate:
call next value for hibernate_sequence
Hibernate:
call next value for hibernate_sequence
Hibernate:
insert
into
institutes
(name, id)
values
(?, ?)
Hibernate:
insert
into
socialprofiles
(institute_id, shortName, id)
values
(?, ?, ?)
可以看出在一对一关系时候,在socialprofiles上添加了一个外键字段institute_id,保存的时候也是将双方关系实体保存。
我们在看看查询
@Test
public void testOneToOneQuery() {
Institute institute = new Institute("深圳研究所");
institute.setSocialProfile(new SocialProfile("深圳研究所-社交账号"));
InstituteDAO dao = new InstituteDAO();
dao.save(institute);
long id = institute.getId();
dao.queryById(id);
}
日志信息:
Hibernate:
select
institute0_.id as id1_1_0_,
institute0_.name as name2_1_0_
from
institutes institute0_
where
institute0_.id=?
Hibernate:
select
socialprof0_.id as id1_2_0_,
socialprof0_.institute_id as institut3_2_0_,
socialprof0_.shortName as shortNam2_2_0_
from
socialprofiles socialprof0_
where
socialprof0_.institute_id=?
可以看出这里我只是查询研究所信息,但是实际上也将社交账号的信息一并查出,这种方式在一些场景中是合适的,但是有的时候我也许需要延迟加载。因此,我总结如下:
1、这种实现方式在某些场景下是合适的,但是存在两个问题。
2、多了一个外键字段,实际上在一对一关系的时候,外键字段是可以共享的。
3、单向关联的时候延迟加载可行,双向关联的时候延迟加载不可用。
双向一对一关联最佳实践
最佳实践的使用方法如下,只需要修改SocialProfile实体就可以啦,代码如下:
import javax.persistence.*;
import java.io.Serializable;
@Entity( name = "SocialProfile")
@Table(name = "socialprofiles")
public class SocialProfile implements Serializable {
@Id
@GeneratedValue
private long id;
private String shortName;
@OneToOne(fetch = FetchType.LAZY)
@MapsId
@JoinColumn(name = "id")
private Institute institute;
public SocialProfile(){}
public SocialProfile(String shortName) {
this.shortName = shortName;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getShortName() {
return shortName;
}
public void setShortName(String shortName) {
this.shortName = shortName;
}
public Institute getInstitute() {
return institute;
}
public void setInstitute(Institute institute) {
this.institute = institute;
}
}
增加使用了一个注解@MapsId,同时将外键映射到id,实际上就是将外键与主键公用。这种方法称为共享主键,也就是一对一的双发实体共享一个主键,在这种情况下,甚至都可以不需要双向关联,有兴趣的可以试试这种情况。