JPA实体映射——Entity Mapping

时间:2022-06-01 21:21:43

学习JPA,实体之间的映射关系是最重要的一环,为了学习和讨论这部分知识,我们将这部分知识分为若干章节进行学习,我们也会用实际的例子进行分析,并且指出存在的问题,然后给出相应的优化方案。

 

第一步,我们列出具体的案例

JPA实体映射——Entity Mapping

我们分别使用了三个实体:Institute(研究所),Department(部门),SocialProfile(社交账号)。如上图所示,研究所和部门是一对多的关系,研究所和社交账号是一对一的关系,部门和社交账号也是一对一的关系。

因此,我们的实体映射关系如下:

社交账号实体

package com.jpa.demo.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;

/**
 * 社交账号实体
 */
@Entity
@Table(name = "social_profiles")
public class SocialProfile implements Serializable {

    @Id
    @GeneratedValue()
    private Long id;

    @Override
    public String toString() {
        return "SocialProfile{" +
                "id=" + id +
                '}';
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
}


部门实体

package com.jpa.demo.model;

import javax.persistence.*;
import java.io.Serializable;

/**
 * 部门实体
 */
@Entity
@Table(name = "departments")
public class Department implements Serializable {
    @Id
    @GeneratedValue
    private Long id = 0L;
    @OneToOne
    private SocialProfile socialProfile;
    @ManyToOne
    private Institute institute;

    @Override
    public String toString() {
        return "Department{" +
                "id=" + id +
                ", socialProfile=" + socialProfile +
                ", institute=" + institute +
                '}';
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public SocialProfile getSocialProfile() {
        return socialProfile;
    }

    public void setSocialProfile(SocialProfile socialProfile) {
        this.socialProfile = socialProfile;
    }

    public Institute getInstitute() {
        return institute;
    }

    public void setInstitute(Institute institute) {
        this.institute = institute;
    }
}

研究所实体

package com.jpa.demo.model;

import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

/**
 * 研究所实体
 */
@Entity
@Table(name = "institutes")
public class Institute implements Serializable {

    @Id
    @GeneratedValue
    private Long id;
    @OneToMany
    private Set<Department> departments = new HashSet<>(0);
    @OneToOne
    private SocialProfile socialProfile;

    @Override
    public String toString() {
        return "Institute{" +
                "id=" + id +
                ", departments=" + departments +
                ", socialProfile=" + socialProfile +
                '}';
    }

    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 SocialProfile getSocialProfile() {
        return socialProfile;
    }

    public void setSocialProfile(SocialProfile socialProfile) {
        this.socialProfile = socialProfile;
    }
}

我们新建一个InstituteDao来插入实体

package com.jpa.demo.dao;

import com.jpa.demo.model.Institute;
import com.jpa.demo.utils.JPAUtil;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;

public class InstituteDao {
    private EntityManagerFactory entityManagerFactory = JPAUtil.getEntityManagerFactory();

    public Long saveInstitute(Institute institute) {
        EntityManager entityManager = null;
        Long id = null;
        try {
            entityManager = this.entityManagerFactory.createEntityManager();
            EntityTransaction tx = entityManager.getTransaction();
            tx.begin();
            entityManager.persist(institute);
            id = institute.getId();
            tx.commit();
        } finally {
            entityManager.close();
        }
        return id;
    }
}

我们写一个测试类InstituteDaoTest

测试代码

package com.jpa.demo.dao;

import com.jpa.demo.model.Institute;
import org.junit.Test;

public class InstituteDaoTest {

    @Test
    public void testSaveInstitute() {
        Institute institute = new Institute();
        InstituteDao dao = new InstituteDao();
        dao.saveInstitute(institute);
    }
}

执行一下,看看会发生什么情况?

日志信息

Hibernate: 
    
    create table departments (
       id bigint not null,
        institute_id bigint,
        socialProfile_id bigint,
        primary key (id)
    )
Hibernate: 
    
    create table institutes (
       id bigint not null,
        socialProfile_id bigint,
        primary key (id)
    )
Hibernate: 
    
    create table institutes_departments (
       Institute_id bigint not null,
        departments_id bigint not null,
        primary key (Institute_id, departments_id)
    )
Hibernate: 
    
    create table social_profiles (
       id bigint not null,
        primary key (id)
    )

Hibernate: 
    
    alter table institutes_departments 
       drop constraint if exists UK_67unewuyoqel2nu2ef8me1hyv
Hibernate: 
    
    alter table institutes_departments 
       add constraint UK_67unewuyoqel2nu2ef8me1hyv unique (departments_id)
Hibernate: create sequence hibernate_sequence start with 1 increment by 1
Hibernate: 
    
    alter table departments 
       add constraint FK7dq7qwom6wham6omx9qjlx9f 
       foreign key (institute_id) 
       references institutes
Hibernate: 
    
    alter table departments 
       add constraint FKk6hmk4i9whav9gyix187816yi 
       foreign key (socialProfile_id) 
       references social_profiles
Hibernate: 
    
    alter table institutes 
       add constraint FKa7euwfagj3fu26etnv0g61a86 
       foreign key (socialProfile_id) 
       references social_profiles
Hibernate: 
    
    alter table institutes_departments 
       add constraint FK3lq3buf7j6k7is3q1lurp2j91 
       foreign key (departments_id) 
       references departments
Hibernate: 
    
    alter table institutes_departments 
       add constraint FKlqld2ri9e0qmvhwy6nnany0o9 
       foreign key (Institute_id) 
       references institutes

Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    insert 
    into
        institutes
        (socialProfile_id, id) 
    values
        (?, ?)

日志分析

从日志可以看出,JPA为我们创建了四张表:departments,institutes, institutes_departments,social_profiles ,然后又创建了一些外键约束,最后插入数据。我们先从映射关系分析。

一对一关系分析

研究所实体和社交账号是一对一关系,所以会在研究所实体上创建外键,如图所示:

JPA实体映射——Entity Mapping

部门实体和社交账号也是一对一关系,所以也会在部门实体上创建外键,如图所示:

JPA实体映射——Entity Mapping

这个映射关系其实不难理解,社交账号一定有一个所属,在我们的例子中,要么所属研究所,要么所属部门,因此设计账号不能直接删除,必须是当删除研究所或者删除部门时,同时删除对应的社交账号。似乎很正确,但是和我们平时设计表的时候不一样,我们以后会提供一种理解起来舒服的优化方案。

一对多关系分析

研究所实体和部门实体是一对多得关系,这里JPA为我们生成了一个中间表institutes_departments 这个表的主键是联合主键,主键的值是两个表对应主键的组合,而departments_id是唯一的,很好,多方唯一。

部门实体对应的表增加了一个指向研究所实体的外键,中间表增加了两个外键分别指向多方和一方的表。

好了,分析到此为止,我们还没有说到保存,删除啥的。暂时不着急,我们先看看JPA生成的表有啥问题没?其实JPA生成的表没问题,只是不够好而言,我们有更好的一对多的映射方案,同时在涉及实体映射关系时,我们可以根据具体情况,选择单向导航还是双向导航?没听说过吧,我们下一节继续。