引言:当Hibernate查询部门对象时,立即查询并加载与之的员工对象,这查询策略是立即加载策略。立即加载存在两大不足:会执行不必要的查询语句,影响性能。可能会加载大量不需要的对象,增加系统开销,浪费内存空间。Hibernate提供了延迟加载策略,延迟加载策略能避免加载应用程序不需要访问的关联对象。
延迟加载级别
- 类级别:<class>元素中lazy属性可选"true"延迟加载和"false"立即加载,默认是"true"延迟加载。
- 一对多和多对多级联级别:<set>元素中lazy属性可选true延迟加载,extra增强延迟加载,false立即加载。默认值true延迟加载
- 多对一关联级别:<many-to-one>元素中lazy属性可选proxy延迟加载,no-proxy无代理延迟加载和false立即加载,默认proxy
类级别加载策略
- 立即加载
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.pojo.Dept" table="dept" catalog="project" lazy="false">
<id name="deptNo" column="deptNo" type="java.lang.Byte">
<generator class="assigned"></generator>
</id>
<property name="deptName" column="deptName" type="java.lang.String"></property>
<property name="location" column="location" type="java.lang.String"></property>
</class>
</hibernate-mapping>
Hibernate:
select
dept0_.deptNo as deptNo5_0_,
dept0_.deptName as deptName5_0_,
dept0_.location as location5_0_
from
project.dept dept0_
where
dept0_.deptNo=?
延迟加载
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.pojo.Dept" table="dept" catalog="project" lazy="true">
<id name="deptNo" column="deptNo" type="java.lang.Byte">
<generator class="assigned"></generator>
</id>
<property name="deptName" column="deptName" type="java.lang.String"></property>
<property name="location" column="location" type="java.lang.String"></property>
</class>
</hibernate-mapping>
org.hibernate.LazyInitializationException: could not initialize proxy - no Session 抛出异常Hibernate类级别采用延迟加载机制load()方法返回的代理对象没有在Session里初始化,所以需要在事务里访问获取对象的属性
public Dept load(Serializable id) {
Transaction tx = null;
Dept dept = new Dept();
try {
tx = deptDao.getCurrentSession().beginTransaction();
dept = deptDao.load(id);
System.out.println(dept.getDeptNo());
tx.commit();
} catch (HibernateException ex) {
ex.printStackTrace();
if (tx != null) {
tx.rollback();
}
}
return dept;
}
总结
1.当Dept类级别采用立即加载策略,seesion.load()会执行访问dept表的select语句,如果是采用延迟加载机制,只会返回一个Dept代理类的实例,他的deptNo属性是10,其余属性都为null,且不会执行select语句。
2.通过load()方法加载的的延迟状态的Dept代理对象,除了OID,其他属性均为null。通过调用其getDeptName()方法等可以促使Hibernate执行查询,获得数据从而完成代理实例的初始化。
3.调用getDeptNo()方法访问OID属性,不会触发Hibernate初始化代理类实例的行为。而是直接返回Dept代理类实例的OID值,无须查询数据库。
4.如果加载的Dept代理实例的OID在数据库中不存在,Session的load方法不会立刻抛出异常,因为并未真正执行查询语句,只有当Hibernate完成Dept实例的初始化时,才会真正执行查询语句,才会抛出异常。org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.pojo.Dept#11]
5.Dept代理对象的实例只有在当前Session范围才能被初始化,如果在当前Session的生命周期内,应用程序没有完成Dept代理实例的初始化工作,那么Session关闭后,试图访问该Dept代理实例OID以外的属性,将抛出异常org.hibernate.LazyInitializationException: could not initialize proxy - no
get()和load()方法区别
- 1.无论Dept.hbm.xml文件的<class>元素的lazy属性true还是false,Session的get()方法在类级别总是使用立即加载策略访问数据库的dept表执行select语句,而load()方法如果在<class>元素的lazy属性是false采用的是立即加载,而true采用的是懒加载机制不会访问数据库dept表不会执行select语句。
- 2.get()方法返回的是一个已经初始化的持久化对象,拥有所有的属性。而在懒加载策略中load()方法返回的是有OIDde的代理对象其他属性都是null只能访问OID,而且需要在Session中初始化,如果不在Session生命周期内初始化会抛出异常org.hibernate.LazyInitializationException: could not initialize proxy - no
- 3.当加载的Dept对象实例的OID在数据库中不存在,get()方法执行完select语句后返回的是null,而load()方法不会立刻抛出异常,当Dept代理对象被初始化时才会抛出异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.pojo.Dept#11]
- 4.get()和load()方法都会优先查询Session缓存,get()方法如果没有命中一级缓存会直接查询数据库执行SQL语句,load()方法没有命中一级缓存后会查询二级缓存,如果二级缓存没有命中则最后直接查询数据库表执行SQL语句
list()和iterate()方法区别
- 1.list()和iterate()方法都是不支持懒加载策略,返回的都是集合对象。list()返回List接口集合,iterate()方法返回Iterator接口集合。
- 2.list()方法返回的List接口集合,获取所有的对象。iterate()方法返回的Iterator接口集合,查询到所有对象的主键值并保存到session缓存中去。如果不在Session中查询集合中的对象就会抛出异常org.hibernate.SessionException: Session is closed!
- 3.list()是直接根据SQL提交到数据库查询,并获取所有的对象。 当再次查询时,然后是根据SQL提交到数据库中查询,并获取所有对象。list()方法不支持一级缓存。iterate分两步,第一步是根据SQL提交到数据库查询到所有对象的主键值,并保存到session缓存中去。 然后根据主键查询获取到所有的对象保存到sesson中。当再次查询时,直接根据主键值查询缓存中的对象,如果存在的话,不再提交到数据库中重新查询了。iterate()方法支持一级缓存。
- 4.list()只执行一条SQL语句,iterate至少执行获取所有对象的主键值的SQL语句,若要在Session中访问集合中的对象时会执行1+N条SQL语句。
Hibernate:
select
department0_.deptNo as col_0_0_
from
project.Department department0_
Hibernate:
select
department0_.deptNo as deptNo0_0_,
department0_.deptName as deptName0_0_,
department0_.location as location0_0_
from
project.Department department0_
where
department0_.deptNo=?
sales
Hibernate:
select
department0_.deptNo as deptNo0_0_,
department0_.deptName as deptName0_0_,
department0_.location as location0_0_
from
project.Department department0_
where
department0_.deptNo=?
sales
Hibernate:
select
department0_.deptNo as deptNo0_0_,
department0_.deptName as deptName0_0_,
department0_.location as location0_0_
from
project.Department department0_
where
department0_.deptNo=?
sales
Hibernate:
select
department0_.deptNo as deptNo0_0_,
department0_.deptName as deptName0_0_,
department0_.location as location0_0_
from
project.Department department0_
where
department0_.deptNo=?
sales
Hibernate:
select
department0_.deptNo as deptNo0_0_,
department0_.deptName as deptName0_0_,
department0_.location as location0_0_
from
project.Department department0_
where
department0_.deptNo=?
研发部
Hibernate:
select
department0_.deptNo as deptNo0_0_,
department0_.deptName as deptName0_0_,
department0_.location as location0_0_
from
project.Department department0_
where
department0_.deptNo=?
研发部
一对多和多对多关联的查询加载策略
立即加载
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.pojo.Department" table="Department" catalog="project">
<id name="deptNo" type="java.lang.Byte" column="deptNo">
<generator class="assigned"></generator>
</id>
<property name="deptName" type="java.lang.String" column="deptName"></property>
<property name="location" type="java.lang.String" column="location"></property>
<set name="emps" cascade="all" inverse="true" lazy="false">
<key column="deptNo"></key>
<one-to-many class="com.pojo.Emp"></one-to-many>
</set>
</class>
</hibernate-mapping>
Hibernate:
select
department0_.deptNo as deptNo0_0_,
department0_.deptName as deptName0_0_,
department0_.location as location0_0_
from
project.Department department0_
where
department0_.deptNo=?
Hibernate:
select
emps0_.deptNo as deptNo0_1_,
emps0_.empNo as empNo1_,
emps0_.empNo as empNo1_0_,
emps0_.empName as empName1_0_,
emps0_.job as job1_0_,
emps0_.salary as salary1_0_,
emps0_.deptNo as deptNo1_0_
from
project.Emp emps0_
where
emps0_.deptNo=?
当执行Session的get()方法时,对于Dept对象采用类级别的时立即加载策略,对于Dept地域性的emps集合采用一对多级别的立即加载策略,会立即加载一个Dept对象和多个Emp对象。
延迟加载
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.pojo.Department" table="Department" catalog="project">
<id name="deptNo" type="java.lang.Byte" column="deptNo">
<generator class="assigned"></generator>
</id>
<property name="deptName" type="java.lang.String" column="deptName"></property>
<property name="location" type="java.lang.String" column="location"></property>
<set name="emps" cascade="all" inverse="true" lazy="true">
<key column="deptNo"></key>
<one-to-many class="com.pojo.Emp"></one-to-many>
</set>
</class>
</hibernate-mapping>
Hibernate:
select
department0_.deptNo as deptNo0_0_,
department0_.deptName as deptName0_0_,
department0_.location as location0_0_
from
project.Department department0_
where
department0_.deptNo=?
当执行Session的get()方法时,对于Dept对象采用类级别的时立即加载策略,对于Dept地域性的emps集合采用一对多级别的延迟加载策略,emps属性引用一个没有被初始化集合代理对象,此时emps集合中没有存放任何的Emp对象,只有当emps集合代理对象被初始化时才回会到数据库中查询所有与Dept关联的Emp对象。在会话关闭之前,应用程序第一次访问它时,调用它的iterator(),size(),isEmpty(),contains()方法即可完成初始化,如果不在会话关闭之前初始化就调用emps集合中的对象就会抛出异常org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.pojo.Department.emps, no session or session was closed。
public Department getDept(Serializable id) {
Transaction tx = null;
Department result = null;
try {
tx = departmentDao.getCurrentSession().beginTransaction();
result = departmentDao.get(id);
Set<Emp> emps=result.getEmps();
Iterator<Emp> empIterator=emps.iterator();
while (empIterator.hasNext())
{
Emp emp=empIterator.next();
System.out.println(emp.getEmpName());
}
tx.commit();
} catch (HibernateException ex) {
ex.printStackTrace();
if (tx != null) {
tx.rollback();
}
}
return result;
}
Hibernate:
select
department0_.deptNo as deptNo0_0_,
department0_.deptName as deptName0_0_,
department0_.location as location0_0_
from
project.Department department0_
where
department0_.deptNo=?
Hibernate:
select
emps0_.deptNo as deptNo0_1_,
emps0_.empNo as empNo1_,
emps0_.empNo as empNo1_0_,
emps0_.empName as empName1_0_,
emps0_.job as job1_0_,
emps0_.salary as salary1_0_,
emps0_.deptNo as deptNo1_0_
from
project.Emp emps0_
where
emps0_.deptNo=?
张三
增强延迟加载
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.pojo.Department" table="Department" catalog="project">
<id name="deptNo" type="java.lang.Byte" column="deptNo">
<generator class="assigned"></generator>
</id>
<property name="deptName" type="java.lang.String" column="deptName"></property>
<property name="location" type="java.lang.String" column="location"></property>
<set name="emps" cascade="all" inverse="true" lazy="extra">
<key column="deptNo"></key>
<one-to-many class="com.pojo.Emp"></one-to-many>
</set>
</class>
</hibernate-mapping>
增强延迟加载策略能进一步的延迟Dept对象的emps集合代理类实例的初始化时机,当程序第一次访问emps属性的iterator()方法时,会导致emps集合代理类实例的初始化,而当程序第一次访问emps属性的size(),contains()和isEmpty()方法时,Hibernate不会初始化emps集合代理实例,仅通过特定的select语句查询必要的信息select count(empNo) from project.Emp where deptNo =?
public Department getDept(Serializable id) {
Transaction tx = null;
Department result = null;
try {
tx = departmentDao.getCurrentSession().beginTransaction();
result = departmentDao.get(id);
Set<Emp> emps=result.getEmps();
int size=emps.size();
System.out.println(size);
tx.commit();
} catch (HibernateException ex) {
ex.printStackTrace();
if (tx != null) {
tx.rollback();
}
}
return result;
}
Hibernate:
select
department0_.deptNo as deptNo0_0_,
department0_.deptName as deptName0_0_,
department0_.location as location0_0_
from
project.Department department0_
where
department0_.deptNo=?
Hibernate:
select
count(empNo)
from
project.Emp
where
deptNo =?
1
配置多对一关联的查询加载策略
延迟加载
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.pojo.Emp" table="Emp" catalog="project">
<id name="empNo" column="empNo" type="java.lang.Integer">
<generator class="assigned"></generator>
</id>
<property name="empName" column="empName" type="java.lang.String"></property>
<property name="job" column="job" type="java.lang.String"></property>
<property name="salary" column="salary" type="java.lang.Double"></property>
<many-to-one name="department" column="deptNo" class="com.pojo.Department" lazy="proxy"></many-to-one>
</class>
</hibernate-mapping>
public Emp get(Serializable id)
{
Transaction tx=null;
Emp emp=new Emp();
try {
tx=empDao.getCurrentSession().beginTransaction();
emp=empDao.get(id);
Department department=emp.getDepartment();//返回Dept代理类实例的引用,OID由emp表的外键值决定
System.out.println(emp.getDepartment().getDeptName());
tx.commit();
}catch (HibernateException ex)
{
ex.printStackTrace();
if(tx!=null)
{
tx.rollback();
}
}
return emp;
}
Hibernate:
select
emp0_.empNo as empNo1_0_,
emp0_.empName as empName1_0_,
emp0_.job as job1_0_,
emp0_.salary as salary1_0_,
emp0_.deptNo as deptNo1_0_
from
project.Emp emp0_
where
emp0_.empNo=?
Hibernate:
select
department0_.deptNo as deptNo0_0_,
department0_.deptName as deptName0_0_,
department0_.location as location0_0_
from
project.Department department0_
where
department0_.deptNo=?
研发部
当执行Session的get()方法时,会立即执行查询Emp对象的select语句,Emp对象的dept属性引用Dept代理类实例,这个代理类的实例的OID由EMP表外键值决定,当执行dept.getDeptName()方法时,Hibernate会初始化Dept代理类实例,执行select语句会从数据库中加载Dept对象。
无代理延迟加载
public Emp get(Serializable id) {
Transaction tx = null;
Emp emp = new Emp();
try {
tx = empDao.getCurrentSession().beginTransaction();
emp = empDao.get(id);
Department department = emp.getDepartment();//返回Dept代理类实例的引用,OID由emp表的外键值决定
System.out.println(department);
tx.commit();
} catch (HibernateException ex) {
ex.printStackTrace();
if (tx != null) {
tx.rollback();
}
}
return emp;
}
如果对Emp对象的dept属性使用无代理对象延迟加载策略,加载Emp对象的dept属性时null,当执行emp.getDept()时才会执行查询dept表的select语句,从而加载Dept对象。但是当lazy属性为"no-proxy"时,需要在编译期间进行字节码增强操作,否则运行情况和lazy属性时"proxy"时相同,debug运行效果。
字节码增强技术相当于是一把打开运行时JVM的钥匙,利用它可以动态地对运行中的程序做修改,也可以跟踪JVM运行中程序的状态。此外,我们平时使用的动态代理、AOP也与字节码增强密切相关,它们实质上还是利用各种手段生成符合规范的字节码文件。综上所述,掌握字节码增强后可以高效地定位并快速修复一些棘手的问题(如线上性能问题、方法出现不可控的出入参需要紧急加日志等问题),也可以在开发中减少冗余代码,大大提高开发效率。
立即加载
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.pojo.Emp" table="Emp" catalog="project">
<id name="empNo" column="empNo" type="java.lang.Integer">
<generator class="assigned"></generator>
</id>
<property name="empName" column="empName" type="java.lang.String"></property>
<property name="job" column="job" type="java.lang.String"></property>
<property name="salary" column="salary" type="java.lang.Double"></property>
<many-to-one name="department" column="deptNo" class="com.pojo.Department" lazy="false"></many-to-one>
</class>
</hibernate-mapping>
Hibernate:
select
emp0_.empNo as empNo1_0_,
emp0_.empName as empName1_0_,
emp0_.job as job1_0_,
emp0_.salary as salary1_0_,
emp0_.deptNo as deptNo1_0_
from
project.Emp emp0_
where
emp0_.empNo=?
Hibernate:
select
department0_.deptNo as deptNo0_0_,
department0_.deptName as deptName0_0_,
department0_.location as location0_0_
from
project.Department department0_
where
department0_.deptNo=?
如果对Emp对象的dept属性使用立即加载策略,会立即获取到一个Dept对象。