JPA实体映射——一对多关系映射(下)

百科知识2025-04-261

接上一节一对多关系的映射学习,今天我们学习一种双向关联的最佳实践,废话不说,先上业务实例图。

业务实例图

###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 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 getDepartments() { return departments; } public void setDepartments(Set 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,相比第二种情况的先更新外键,然后删除,这种方式再次做到了优化。

总结如下:在一对多的关联关系中,如果您需要在一方用集合关联多方,最好的实践是采用我们推荐的第三种方式。有一点需要切记的是,只有在多方对象的数量比较少的时候,推荐使用以上的方式,如果多方数量比较多,那么应该怎么处理呢?这个问题我们以后分享。