JPA实体映射——一对多关系映射(下)
接上一节一对多关系的映射学习,今天我们学习一种双向关联的最佳实践,废话不说,先上业务实例图。
业务实例图
下面用代码说明双向关联的一对多关系
###研究所实体 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研究所查询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,相比第二种情况的先更新外键,然后删除,这种方式再次做到了优化。
总结如下:在一对多的关联关系中,如果您需要在一方用集合关联多方,最好的实践是采用我们推荐的第三种方式。有一点需要切记的是,只有在多方对象的数量比较少的时候,推荐使用以上的方式,如果多方数量比较多,那么应该怎么处理呢?这个问题我们以后分享。