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

时间:2022-06-01 21:21:55

紧接上一节的学习,我们还是首先把业务关系图整理出来。

业务案例图

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

上一节我们知道,采用单向关联如果不使用@JoinColumn时,在新增一个研究所实体的时候,会生成三张表,并且会执行多条插入语句,在使用@JoinColumn时,会生成两张表,同时在departments上生成一个department_id字段,而JPA在插入数据时,采用的是先插入部门表数据,然后更新外键字段,这种方式,插入的效率不是很高。

今天我们来看看查询的逻辑:

代码实例

InstituteDAO增加一个查询Institute的方法:

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);
        tx.commit();
    } finally {
        entityManager.close();
    }
    return institute;
}

增加测试方法:

@Test
public void testQueryInstitute() {
    Institute institute = new Institute("深圳研究所");
    institute.getDepartments().add(new Department("深圳研究所1部"));
    institute.getDepartments().add(new Department("深圳研究所2部"));
    institute.getDepartments().add(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=?

可以看出,JPA默认的加载机制是延迟加载,因此当我们获取研究所对象的时候,只是获取了研究所而没有获取部门集合。

好的,那我就访问一下部门信息:

修改代码如下:

我在测试方法testQueryInstitute()增加两行

@Test
public void testQueryInstitute() {
    Institute institute = new Institute("深圳研究所");
    institute.getDepartments().add(new Department("深圳研究所1部"));
    institute.getDepartments().add(new Department("深圳研究所2部"));
    institute.getDepartments().add(new Department("深圳研究所3部"));

    InstituteDao dao = new InstituteDao();
    dao.save(institute);
    Long id = institute.getId();
    Institute newInstitute = dao.queryById(id);
    System.out.println("newInstitute"+newInstitute);
    int size = newInstitute.getDepartments().size();
    System.out.println("departments size:"+size);
}

此时,我发现抛出了一个异常:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.jpa.demo.model.undirectional.Institute.departments, could not initialize proxy - no Session

	at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:606)
	at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:218)
	at org.hibernate.collection.internal.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:162)
	at org.hibernate.collection.internal.PersistentSet.size(PersistentSet.java:168)
	at com.jpa.demo.dao.undirectional.InstituteDaoTest.testQueryInstitute(InstituteDaoTest.java:37)

可以看出没有初始化一个代理类,因此在此情况下,延迟加载似乎不起作用。

理解LazyInitializationException

首先,我们需要理解这个异常产生的原因,然后再用代码去验证相关的结论,这里有一个对此原因解释的链接,我们需要理解的就是:

t's important to understand what is Session, Lazy Initialisation, and Proxy Object and how they come together in the Hibernate framework.

  • Session is a persistence context that represents a conversation between an application and the database
  • Lazy Loading means that the object will not be loaded to the Session context until it is accessed in code.
  • Hibernate creates a dynamic Proxy Object subclass that will hit the database only when we first use the object.

This error means that we try to fetch a lazy-loaded object from the database by using a proxy object, but the Hibernate session is already closed.

其实就是说,我在session外获取延迟加载的对象就会导致这个错误。

有了这个思路,我就有了两种做法:

1、在session内访问延迟加载的对象

2、放弃延迟加载,使用及时记载

session内部访问延迟加载对象

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;
}

日志信息如下:

Hibernate: 
   select
       institute0_.id as id1_1_0_,
       institute0_.name as name2_1_0_ 
   from
       institutes institute0_ 
   where
       institute0_.id=?
Hibernate: 
   select
       department0_.department_id as departme3_0_0_,
       department0_.id as id1_0_0_,
       department0_.id as id1_0_1_,
       department0_.name as name2_0_1_ 
   from
       departments department0_ 
   where
       department0_.department_id=?
departments size:3
newInstitutecom.jpa.demo.model.undirectional.Institute@1faf386c

可以看出,这次可以获取到部门数量信息,并且不在报错误。同理也可以将研究所这个实体配置成及时加载,感兴趣的可以试试,同样不会报错误。

 

我们再试试删除情况

我增加一个删除方法,显然,这次删除也必须放在session里哈。

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.getDepartments().remove(new Department("深圳研究所2部"));
        tx.commit();
    } finally {
        entityManager.close();
    }
    return institute;
}

测试方法如下:

@Test
public void testDeleteDepartment() {
    Institute institute = new Institute("深圳研究所");
    institute.getDepartments().add(new Department("深圳研究所1部"));
    institute.getDepartments().add(new Department("深圳研究所2部"));
    institute.getDepartments().add(new Department("深圳研究所3部"));

    InstituteDao dao = new InstituteDao();
    dao.save(institute);
    Long id = institute.getId();
    Institute newInstitute = 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_.department_id as departme3_0_0_,
       department0_.id as id1_0_0_,
       department0_.id as id1_0_1_,
       department0_.name as name2_0_1_ 
   from
       departments department0_ 
   where
       department0_.department_id=?
Hibernate: 
   update
       departments 
   set
       department_id=null 
   where
       department_id=? 
       and id=?
Hibernate: 
   delete 
   from
       departments 
   where
       id=?

从查询可以看出是延迟加载,而从删除可以看出JPA的逻辑是先将外键字段更新为空,然后再删除部门数据。

因此我们可以得出结论,单向关联且使用@JoinColumn注解时,JPA在保存和删除的时候,可以使用但是效率不高。