Hibernate的延迟加载

简介

Hibernate通过延迟加载在真正需要使用数据时从数据库表中加载,这样可以加快程序运行速度,减少内存开销。Hibernate在以下三种情况会默认使用延迟加载:

  1. load方法延迟加载;
  2. 实体属性延迟加载;
  3. 集合属性延迟加载。

数据库示例

1
示例数据库包含3张表:

  1. 文章表(post),存储文章标题、内容,主键为自增整数;
  2. 分类表(category),存储分类名称,主键为自增整数;
  3. 标签表(score),存储标签名称,主键为自增整数;
  4. 文章标签表(post_tag),存储文章包含的标签,使用文章id和标签id作为联合主键。

每篇文章属于一个分类,并包含多个标签。

延迟加载示例

创建Post类与post表映射。
Post.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package com.magicwt.bean;

import java.util.Set;

public class Post {

//与主键id映射
private int id;
//与字段title映射
private String title;
//与字段content映射
private String content;
//关联Category实体
private Category category;
//关联Tag实体
private Set<Tag> tags;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}

public String getContent() {
return content;
}

public void setContent(String content) {
this.content = content;
}

public Category getCategory() {
return category;
}

public void setCategory(Category category) {
this.category = category;
}

public Set<Tag> getTags() {
return tags;
}

public void setTags(Set<Tag> tags) {
this.tags = tags;
}

}

Post.hbm.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?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.magicwt.bean.Post" table="post">
<id name="id">
<column name="id" sql-type="int" not-null="true"/>
</id>
<property name="title">
<column name="title" sql-type="varchar" length="16"/>
</property>
<property name="content">
<column name="content" sql-type="text" length="65535"/>
</property>
<!-- 实体属性,对应于一个分类实体 -->
<one-to-one name="category" class="com.magicwt.bean.Category" constrained="true"/>
<!-- 集合属性,对应于多个标签实体 -->
<set name="tags" table="post_tag" inverse="true">
<key column="post_id"/>
<many-to-many column="tag_id" class="com.magicwt.bean.Tag"/>
</set>
</class>
</hibernate-mapping>

创建Category类与category表映射。
Category.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.magicwt.bean;

public class Category {

//与主键id映射
private int id;
//与字段name映射
private String name;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

}

Category.hbm.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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.magicwt.bean.Category" table="category">
<id name="id">
<column name="id" sql-type="int" not-null="true"/>
</id>
<property name="name">
<column name="name" sql-type="varchar" length="16"/>
</property>
</class>
</hibernate-mapping>

创建Tag类与tag表映射。
Tag.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.magicwt.bean;

public class Tag {

//与主键id映射
private int id;
//与字段name映射
private String name;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

}

Tag.hbm.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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.magicwt.bean.Tag" table="tag">
<id name="id">
<column name="id" sql-type="int" not-null="true"/>
</id>
<property name="name">
<column name="name" sql-type="varchar" length="16"/>
</property>
</class>
</hibernate-mapping>

Hibernate配置文件hibernate.cfg.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://xxx.xxx.xxx.xxx:3306/test</property>
<property name="connection.username">xxx</property>
<property name="connection.password">xxx</property>
<property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>
<property name="show_sql">true</property>
<property name="hibernate.format_sql">true</property>
<mapping resource="mapper/Category.hbm.xml"/>
<mapping resource="mapper/Post.hbm.xml"/>
<mapping resource="mapper/Tag.hbm.xml"/>
</session-factory>
</hibernate-configuration>

load方法延迟加载

创建测试类Test:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.magicwt;

import com.magicwt.bean.Post;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.classic.Session;

public class Test {

public static void main(String[] args) {
SessionFactory sessionFactory = (new Configuration()).configure().buildSessionFactory();
Session session = sessionFactory.openSession();
//使用load方法加载id为1的文章
Post post = (Post)session.load(Post.class, Integer.valueOf(1));
//输出post实例的类名
System.out.println("class of post: " + post.getClass().getCanonicalName());
//取出文章标题并输出
System.out.println(post.getTitle());
}

}

执行main方法,输出如下:

class of post: com.magicwt.bean.Post$$EnhancerByCGLIB$$70a21d16
Hibernate:
select
​ post0_.id as id1_0_,
​ post0_.title as title1_0_,
​ post0_.content as content1_0_
from
​ post post0_
where
​ post0_.id=?
title of post: 测试标题1

从中可以看出,load方法返回的是通过动态代理实现的代理类实例。通过断点可以观察到,执行“post.getTitle()”前,post实例各属性都是默认值(null或0),执行“post.getTitle()”后,才真正从数据库表中取出数据并输出。
将load方法修改为get方法并重新执行,输出如下:

Hibernate:
select
​ post0_.id as id1_0_,
​ post0_.title as title1_0_,
​ post0_.content as content1_0_
from
​ post post0_
where
​ post0_.id=?
class of post: com.magicwt.bean.Post
title of post: 测试标题1

从中可以看出,get方法返回的是Post类实例。通过断点可以观察到,get方法返回的post实例各属性已从数据库表中取出对应的值,get方法并没有延迟加载。

实体属性延迟加载

修改测试类Test:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.magicwt;

import com.magicwt.bean.Post;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.classic.Session;

public class Test {

public static void main(String[] args) {
SessionFactory sessionFactory = (new Configuration()).configure().buildSessionFactory();
Session session = sessionFactory.openSession();
//使用load方法加载id为1的文章
Post post = (Post)session.get(Post.class, Integer.valueOf(1));
//输出post实例的类名
System.out.println("class of post: " + post.getClass().getCanonicalName());
//输出post实例category属性的类名
System.out.println("class of category: " + post.getCategory().getClass().getCanonicalName());
//输出分类名称
System.out.println("name of category:" + post.getCategory().getName());
}

}

执行main方法,输出如下:

Hibernate:
select
​ post0_.id as id1_0_,
​ post0_.title as title1_0_,
​ post0_.content as content1_0_
from
​ post post0_
where
​ post0_.id=?
class of post: com.magicwt.bean.Post
class of category: com.magicwt.bean.Category$$EnhancerByCGLIB$$483428f6
Hibernate:
select
​ category0_.id as id0_0_,
​ category0_.name as name0_0_
from
​ category category0_
where
​ category0_.id=?
name of category: 测试分类1

从中可以看出,get方法返回的post实例,其属性category是代理类实例,并没有真正读取category表中的数据,只有在输出分类名称时,才读取category表中的数据。

集合属性延迟加载

修改测试类Test:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.magicwt;

import com.magicwt.bean.Post;
import com.magicwt.bean.Tag;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.classic.Session;

public class Test {

public static void main(String[] args) {
SessionFactory sessionFactory = (new Configuration()).configure().buildSessionFactory();
Session session = sessionFactory.openSession();
//使用load方法加载id为1的文章
Post post = (Post)session.get(Post.class, Integer.valueOf(1));
//输出post实例的类名
System.out.println("class of post: " + post.getClass().getCanonicalName());
//输出post实例tags属性的类名
System.out.println("class of tags: " + post.getTags().getClass().getCanonicalName());
//输出标签名称
for (Tag tag : post.getTags()) {
System.out.println("name of tag: " + tag.getName());
}
}

}

执行main方法,输出如下:

Hibernate:
select
​ post0_.id as id1_0_,
​ post0_.title as title1_0_,
​ post0_.content as content1_0_
from
​ post post0_
where
​ post0_.id=?
class of post: com.magicwt.bean.Post
class of tags: org.hibernate.collection.PersistentSet
Hibernate:
select
​ tags0_.post_id as post1_1_,
​ tags0_.tag_id as tag2_1_,
​ tag1_.id as id3_0_,
​ tag1_.name as name3_0_
from
​ post_tag tags0_
left outer join
​ tag tag1_
​ on tags0_.tag_id=tag1_.id
where
​ tags0_.post_id=?
name of tag: 测试标签1
name of tag: 测试标签2

从中可以看出,get方法返回的post实例,其属性tags是PersistentSet实例,并没有真正读取tag表中的数据,只有在输出标签名称时,才读取tag表中的数据。