接上一节一对多关系的映射学习,今天我们学习一种双向关联的最佳实践,废话不说,先上业务实例图。
业务实例图
Bidirectional @OneToMany
下面用代码说明双向关联的一对多关系
研究所实体
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);
private String name;
public Institute(){}
public Institute(String name) {
this.name = name;
}
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;
}
}
部门实体
import javax.persistence.*;
import java.io.Serializable;
import java.util.Objects;
@Entity(name = "Department")
@Table(name = "departments")
public class Department implements Serializable {
@Id
@GeneratedValue
private Long id = 0L;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
private Institute institute;
public Department(){}
public void setId(Long id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setInstitute(Institute institute) {
this.institute = institute;
}
public Department(String name){
this.name = name;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public Institute getInstitute() {
return institute;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Department)) return false;
Department that = (Department) o;
return name.equals(that.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
研究所查询DAO
import com.jpa.demo.model.bidirectional.Department;
import com.jpa.demo.model.bidirectional.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 save(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;
}
public Institute queryById(Long id) {
EntityManager entityManager = null;
Institute institute = null;
try {
entityManager = this.entityManagerFactory.createEntityManager();
EntityTransaction tx = entityManager.getTransaction();
tx.begin();
institute = entityManager.find(Institute.class,id);
// int size = institute.getDepartments().size();
// System.out.println("departments size:"+size);
tx.commit();
} finally {
entityManager.close();
}
return institute;
}
public Institute deleteOneById(Long id) {
EntityManager entityManager = null;
Institute institute = null;
try {
entityManager = this.entityManagerFactory.createEntityManager();
EntityTransaction tx = entityManager.getTransaction();
tx.begin();
institute = entityManager.find(Institute.class,id);
institute.removeDepartment(new Department("深圳研究所2部"));
tx.commit();
} finally {
entityManager.close();
}
return institute;
}
}
注意几点:
1、在部门实体的多对一关系上,我们添加了延迟加载,如果不使用这个注解属性,则默认是即使记载。
2、在研究所实体上我们添加了两个自定义方法addDepartment
和removeDepartment
,它们用于同步双方的双向关联。在使用双向关联的时候务必这样做。
3、在部门实体中覆盖实现了equal和hashCode方法,它们用于添加和移除集合中的对象,请务必实现自己的这两个自定义方法。
测试代码
首先我们测试保存的情况:
@Test
public void testSaveInstituteBi() {
Institute institute = new Institute("深圳研究所");
institute.addDepartment(
new Department("深圳研究所1部")
);
institute.addDepartment(
new Department("深圳研究所2部")
);
institute.addDepartment(
new Department("深圳研究所3部")
);
InstituteDAO dao = new InstituteDAO();
dao.save(institute);
}
然后查看日志信息情况:
Hibernate:
create table departments (
id bigint not null,
name varchar(255),
institute_id bigint,
primary key (id)
)
Hibernate:
create table institutes (
id bigint not null,
name varchar(255),
primary key (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:
call next value for hibernate_sequence
Hibernate:
call next value for hibernate_sequence
Hibernate:
call next value for hibernate_sequence
Hibernate:
call next value for hibernate_sequence
Hibernate:
insert
into
institutes
(name, id)
values
(?, ?)
Hibernate:
insert
into
departments
(institute_id, name, id)
values
(?, ?, ?)
Hibernate:
insert
into
departments
(institute_id, name, id)
values
(?, ?, ?)
Hibernate:
insert
into
departments
(institute_id, name, id)
values
(?, ?, ?)
从日志信息可以看出,此时的表结构基本一致,外键也是添加了一个institute_id
,对应于institute
表的主键。而执行保存的SQL语句也和单向关联结合@JoinColumn
一致。
其次我们测试查询
@Test
public void testQueryInstituteBi() {
Institute institute = new Institute("深圳研究所");
institute.addDepartment(
new Department("深圳研究所1部")
);
institute.addDepartment(
new Department("深圳研究所2部")
);
institute.addDepartment(
new Department("深圳研究所3部")
);
InstituteDAO dao = new InstituteDAO();
dao.save(institute);
Long id = institute.getId();
Institute newInstitute = dao.queryById(id);
System.out.println("newInstitute"+newInstitute);
}
这里我们只看查询的日志信息
Hibernate:
select
institute0_.id as id1_1_0_,
institute0_.name as name2_1_0_
from
institutes institute0_
where
institute0_.id=?
可以看出延迟加载是生效的,我们也试试在session中获取部门的数据,将InstituteDAO
中queryById
中的注释打开:
int size = institute.getDepartments().size();
System.out.println("departments size:"+size);
此时,执行的日志信息如下:
Hibernate:
select
institute0_.id as id1_1_0_,
institute0_.name as name2_1_0_
from
institutes institute0_
where
institute0_.id=?
Hibernate:
select
department0_.institute_id as institut3_0_0_,
department0_.id as id1_0_0_,
department0_.id as id1_0_1_,
department0_.institute_id as institut3_0_1_,
department0_.name as name2_0_1_
from
departments department0_
where
department0_.institute_id=?
departments size:3
newInstitutecom.jpa.demo.model.bidirectional.Institute@787508ca
从日志可以看出,只有在获取部门信息时,才发出了查询部门的SQL,可见延迟加载效果显著。
最后我们试试删除部门集合数据
@Test
public void testDeleteInstituteBi() {
Institute institute = new Institute("深圳研究所");
institute.addDepartment(
new Department("深圳研究所1部")
);
institute.addDepartment(
new Department("深圳研究所2部")
);
institute.addDepartment(
new Department("深圳研究所3部")
);
InstituteDAO dao = new InstituteDAO();
dao.save(institute);
Long id = institute.getId();
dao.deleteOneById(id);
}
日志信息如下:
Hibernate:
select
institute0_.id as id1_1_0_,
institute0_.name as name2_1_0_
from
institutes institute0_
where
institute0_.id=?
Hibernate:
select
department0_.institute_id as institut3_0_0_,
department0_.id as id1_0_0_,
department0_.id as id1_0_1_,
department0_.institute_id as institut3_0_1_,
department0_.name as name2_0_1_
from
departments department0_
where
department0_.institute_id=?
Hibernate:
delete
from
departments
where
id=?
从以上信息可以看出,删除部门集合的时候只发出了一条SQL,相比第二种情况的先更新外键,然后删除,这种方式再次做到了优化。
总结如下:在一对多的关联关系中,如果您需要在一方用集合关联多方,最好的实践是采用我们推荐的第三种方式。有一点需要切记的是,只有在多方对象的数量比较少的时候,推荐使用以上的方式,如果多方数量比较多,那么应该怎么处理呢?这个问题我们以后分享。