JPA实体映射——一对多关系映射(中)
紧接上一节的学习,我们还是首先把业务关系图整理出来。
业务案例图
<figure class="image"></figure>上一节我们知道,采用单向关联如果不使用@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在保存和删除的时候,可以使用但是效率不高。