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

时间:2022-06-01 16:40:10

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

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

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

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