JPA实体映射——一对一关系映射

百科知识2025-04-261

前几节我们介绍了一对多的关系,今天我们学习一对一关系以及这种映射方式的最佳实践,先上业务实例图。

从图中可以看出,研究所和社交账号有一对一的关系,部门和社交账号也有一对一的关系,我们选用研究所和社交账号的关系来说明问题。

###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 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 getDepartments() { return departments; } public void setDepartments(Set 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,实际上就是将外键与主键公用。这种方法称为共享主键,也就是一对一的双发实体共享一个主键,在这种情况下,甚至都可以不需要双向关联,有兴趣的可以试试这种情况。