学习JPA,实体之间的映射关系是最重要的一环,为了学习和讨论这部分知识,我们将这部分知识分为若干章节进行学习,我们也会用实际的例子进行分析,并且指出存在的问题,然后给出相应的优化方案。
第一步,我们列出具体的案例
我们分别使用了三个实体: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为我们生成了一个中间表institutes_departments
这个表的主键是联合主键,主键的值是两个表对应主键的组合,而departments_id
是唯一的,很好,多方唯一。
部门实体对应的表增加了一个指向研究所实体的外键,中间表增加了两个外键分别指向多方和一方的表。
好了,分析到此为止,我们还没有说到保存,删除啥的。暂时不着急,我们先看看JPA生成的表有啥问题没?其实JPA生成的表没问题,只是不够好而言,我们有更好的一对多的映射方案,同时在涉及实体映射关系时,我们可以根据具体情况,选择单向导航还是双向导航?没听说过吧,我们下一节继续。