hibernate关于延迟加载(lazy)和强制加载(Hibernate.initialize(Object proxy) )

首先声明这篇文章并非本人所作,只是感觉别人对hibernate 理解的比较透,所以copy过来和大家一起分享

PO 即Persistence Object

VO即ValueObject

PO和VO是Hibernate中两个比较关键的概念。

首先,何谓VO,很简单,VO就是一个简单的值对象。

如:

TUseruser=newTUser();

user.setName("Emma");

这里的user就是一个VO。VO只是简单携带了对象的一些属性信息。

何谓PO?即纳入Hibernate管理框架中的VO。看下面两个例子:

TUseruser=newTUser();

TUseranotherUser=newTUser();

user.setName("Emma");

anotherUser.setName("Kevin");

//此时user和anotherUser都是VO

Transactiontx=session.beginTransaction();

session.save(user);

//此时的user已经经过Hibernate的处理,成为一个PO,而anotherUser仍然是个VO

tx.commit();

//事务提交之后,库表中已经插入一条用户”Emma”的记录,对于anotherUser则无任何操作

Transactiontx=session.beginTransaction();

user.setName("Emma_1");//PO

anotherUser.setName("Kevin_1");//VO

tx.commit();

//事务提交之后,PO的状态被固化到数据库中,也就是说数据库中“Emma”的用户记录已经被更新为“Emma_1”,此时anotherUser仍然是个普通Java对象,它的属性更改不会对数据库产生任何影响,另外,通过Hibernate返回的对象也是PO:由Hibernate返回的PO,如:

TUseruser=(TUser)session.load(TUser.class,newInteger(1));

VO经过Hibernate进行处理,就变成了PO。上面的示例代码session.save(user)中,我们把一个VO“user”传递给Hibernate的Session.save方法进行保存。在save方法中,Hibernate对其进

行如下处理:

1.在当前session所对应的实体容器(EntityMap)中查询是否存在user对象的引用。

2.如果引用存在,则直接返回user对象id,save过程结束.Hibernate中,针对每个Session有一个实体容器(实际上是一个Map对象),如果此容器中已经保存了目标对象的引用,那么hibernate会认为此对象已经与Session相关联。

对于save操作而言,如果对象已经与Session相关联(即已经被加入Session的实体容器中),则无需进行具体的操作。因为之后的Session.flush过程中,Hibernate会对此实体容器中的对象进行遍历,查找出发生变化的实体,生成并执行相应的update语句。

3.如果引用不存在,则根据映射关系,执行insert操作。

a)在我们这里的示例中,采用了native的id生成机制,因此hibernate会从数据库取得insert操作生成的id并赋予user对象的id属性。

b)将user对象的引用纳入Hibernate的实体容器。

c)save过程结束,返回对象id.

而Session.load方法中,再返回对象之前,Hibernate就已经将此对象纳入其实体容器中。

VO和PO的主要区别在于:

.VO是独立的JavaObject。

.PO是由Hibernate纳入其实体容器(EntityMap)的对象,它代表了与数

据库中某条记录对应的Hibernate实体,PO的变化在事务提交时将反应到实

际数据库中。如果一个PO与Session对应的实体容器中分离(如Session关闭后的PO),那么

此时,它又会变成一个VO。

关于unsaved-value

在非显示数据保存时,Hibernate将根据这个值来判断对象是否需要保存。

所谓显式保存,是指代码中明确调用session的save、update、saveOrupdate方法对对象进行持久化。如:

session.save(user);

而在某些情况下,如映射关系中,Hibernate根据级联(Cascade)关系对联接类进行保存。此时代码中没有针对级联对象的显示保存语句,需要Hibernate根据对象当前状

态判断是否需要保存到数据库。此时,Hibernate即将根据unsaved-value进行判定。

首先Hibernate会取出目标对象的id。之后,将此值与unsaved-value进行比对,如果相等,则认为目标对象尚未保存,否则,认为对象已经保存,无需再进行保存操作。

如:user对象是之前由hibernate从数据库中获取,同时,此user对象的若干个关联对象address也被加载,此时我们向user对象新增一个address对象,此时调用

session.save(user),hibernate会根据unsaved-value判断user对象的数个address

关联对象中,哪些需要执行save操作,而哪些不需要。

对于我们新加入的address对象而言,由于其id(Integer型)尚未赋值,因此为null,与我们设定的unsaved-value(null)相同,因此hibernate将其视为一个未保存对象,将为其生成insert语句并执行。

这里可能会产生一个疑问,如果“原有”关联对象发生变动(如user的某个“原有”的address对象的属性发生了变化,所谓“原有”即此address对象已经与user相关联,而不是我们在此过程中为之新增的),此时id值是从数据库中读出,并没有发生改变,自然

与unsaved-value(null)也不一样,那么Hibernate是不是就不保存了?

上面关于PO、VO的讨论中曾经涉及到数据保存的问题,实际上,这里的“保存”,实际上是“insert”的概念,只是针对新关联对象的加入,而非数据库中原有关联对象的

“update”。所谓新关联对象,一般情况下可以理解为未与Session发生关联的VO。而“原有”关联对象,则是PO。:

对于save操作而言,如果对象已经与Session相关联(即已经被加入Session的实体容器中),则无需进行具体的操作。因为之后的Session.flush过程中,Hibernate

会对此实体容器中的对象进行遍历,查找出发生变化的实体,生成并执行相应的update语句。

Inverse和Cascade

Inverse,直译为“反转”。在Hibernate语义中,Inverse指定了关联关系中的方向。关联关系中,inverse=”false”的为主动方,由主动方负责维护关联关系。具体可参见一对多关系中的描述。

而Cascade,译为“级联”,表明对象的级联关系,如TUser的Cascade设为all,就表明如果发生对user对象的操作,需要对user所关联的对象也进行同样的操作。如对user对象执行save操作,则必须对user对象相关联的address也执行save操作。

初学者常常混淆inverse和cascade,实际上,这是两个互不相关的概念。Inverse指的是关联关系的控制方向,而cascade指的是层级之间的连锁操作。

延迟加载(LazyLoading)

为了避免一些情况下,关联关系所带来的无谓的性能开销。Hibernate引入了延迟加载的概念。如,示例中user对象在加载的时候,会同时读取其所关联的多个地址(address)对象,

对于需要对address进行操作的应用逻辑而言,关联数据的自动加载机制的确非常有效。但是,如果我们只是想要获得user的性别(sex)属性,而不关心user的地址(address)

信息,那么自动加载address的特性就显得多余,并且造成了极大的性能浪费。为了获得user的性别属性,我们可能还要同时从数据库中读取数条无用的地址数据,这导致了大量无谓的系统开销。

延迟加载特性的出现,正是为了解决这个问题。所谓延迟加载,就是在需要数据的时候,才真正执行数据加载操作。

对于我们这里的user对象的加载过程,也就意味着,加载user对象时只针对其本身的属性,而当我们需要获取user对象所关联的address信息时(如执行user.getAddresses时),才

真正从数据库中加载address数据并返回。

尝试执行以下代码:

Criteriacriteria=session.createCriteria(TUser.class);

criteria.add(Expression.eq("name","Erica"));

ListuserList=criteria.list();

TUseruser=(TUser)userList.get(0);

System.out.println("Username=>"+user.getName());

Sethset=user.getAddresses();

session.close();//关闭Session

TAddressaddr=(TAddress)hset.toArray()[0];

System.out.println(addr.getAddress());

运行时抛出异常:

LazyInitializationException-Failedtolazilyinitializeacollection-nosessionorsessionwasclosed

如果我们稍做调整,将session.close放在代码末尾,则不会发生这样的问题。这意味着,只有我们实际加载user关联的address时,Hibernate才试图通过

session从数据库中加载实际的数据集,而由于我们读取address之前已经关闭了session,所以报出session已关闭的错误。

这里有个问题,如果我们采用了延迟加载机制,但希望在一些情况下,实现非延迟加载时的功能,也就是说,我们希望在Session关闭后,依然允许操作user的addresses

属性。如,为了向View层提供数据,我们必须提供一个完整的User对象,包含其所关联的address信息,而这个User对象必须在Session关闭之后仍然可以使用。

Hibernate.initialize方法可以通过强制加载关联对象实现这一功能:

Hibernate.initialize(user.getAddresses());

session.close();

//通过Hibernate.initialize方法强制读取数据

//addresses对象即可脱离session进行操作

Sethset=user.getAddresses();

TAddressaddr=(TAddress)hset.toArray()[0];

System.out.println(addr.getAddress());

为了实现透明化的延迟加载机制,hibernate进行了大量努力。其中包括JDKCollection接口的独立实现。

如果我们尝试用HashSet强行转化Hibernate返回的Set型对象:

Sethset=(HashSet)user.getAddresses();

就会在运行期得到一个java.lang.ClassCastException,实际上,此时返回的是一个Hibernate的特定Set实现“net.sf.hibernate.collection.Set”对象,而非

传统意义上的JDKSet实现。这也正是我们为什么在编写POJO时,必须用JDKCollection接口(如Set,Map),而非特定的JDKCollection实现类(如HashSet、HashMap)申明Collection属性的原因。

相关推荐