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

时间:2022-06-01 12:57:25

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

业务实例图

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

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、在研究所实体上我们添加了两个自定义方法addDepartmentremoveDepartment,它们用于同步双方的双向关联。在使用双向关联的时候务必这样做。

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中获取部门的数据,将InstituteDAOqueryById中的注释打开:

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,相比第二种情况的先更新外键,然后删除,这种方式再次做到了优化。

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