本篇博客是之前博客hibernate关联对象的增删改查------查 的后继,本篇代码的设定都在前文已经写好,因此读这篇之前,请先移步上一篇博客

   


   //代码片5
   

SessionFactory sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
Session session = sessionFactory.getCurrentSession();
session.beginTransaction();

Dream d=new Dream();
d.setDescription("marry glt");

Person p=new Person();
p.setName("dlf");

d.setPerson(p);
session.save(d);
session.save(p);
session.getTransaction().commit();

session = sessionFactory.getCurrentSession();
session.beginTransaction();
Dream dream=(Dream) session.get(Dream.class, 1);
System.out.println(dream.getPerson().getName()+" ddddd");
session.getTransaction().commit();



对应代码5而言,get dream的时候发的sql语句为


select
dream0_.id as id0_1_,
dream0_.description as descript2_0_1_,
dream0_.personId as personId0_1_,
person1_.id as id1_0_,
person1_.myname as myname1_0_
from
Dream dream0_
left outer join
Person person1_
on dream0_.personId=person1_.id
where
dream0_.id=?



换言之,get dream的时候,我们也获得了对应的person。


有3个问题


1

执行代码5的时候,dream的manytoone的cascade已经去掉了。


2 如果把Dream dream=(Dream) session.get(Dream.class, 1);与之前保存对象的语句放到一个session里,发的sql语句竟然是update。但是,System.out.println(dream.getPerson().getName()+"  ddddd");依然能输入person的name。



我们能得出规律


在多对一,一对多的情况下,当我们读取多的一方时,默认也会读取一的一方。(这个规律与cascade无关)


反过来,如果我读一的一方呢,会不会也自动读出多的一方呢?


我们看代码


//代码6
public void testGetPerson() {
Session s = sessionFactory.getCurrentSession();
s.beginTransaction();
Person p = (Person)s.get(Person.class, 2);
s.getTransaction().commit();

}

这个时候它发的sql语句是:


select
person0_.id as id1_0_,
person0_.myname as myname1_0_
from
Person person0_
where
person0_.id=?

并没有去主动获得person对应的dream。


那如果我想获得dream呢?


hibernate的管理关系中还有一个参数叫fetch。它管的就是在读取某个对象时,是否需要读取与之相关的另一个对象。


通过查阅api文档,我们知道fetch的取值是fetchtype型的。而fetchtype是个Enum类型的。

hibernate关联对象的增删改查------查_源码

hibernate关联对象的增删改查------查_数据库_02

可以取值LAZY与EAGER。



这个两个值是什么意思?


猜一猜,我们大概都是知道,eager就是主动获取关联对象的数据,lazy就是不获取么。


我只能说,大概是对的。


我们看代码


在Person里面修改OneToMany的属性。


@OneToMany(mappedBy="person",fetch=FetchType.EAGER)
public Set<Dream> getDreams() {
return dreams;
}

此时再运行代码6,发的sql语句就是:


select
person0_.id as id1_1_,
person0_.myname as myname1_1_,
dreams1_.personId as personId3_,
dreams1_.id as id3_,
dreams1_.id as id0_0_,
dreams1_.description as descript2_0_0_,
dreams1_.personId as personId0_0_
from
Person person0_
left outer join
Dream dreams1_
on person0_.id=dreams1_.personId
where
person0_.id=?

我们就能知道,如果想在取一的时候同时取多的一方,就在一的一方上加上fetch=feachType.eager。


那么根据前面的代码,我们就能推测出来


在默认情况下


一的那一方的fetch是lazy

多的那一方的fetch是eager

用eager修饰关联关系:hibernate会发关联的sql

用lazy修饰关联关系:hibernate不会主动发关联sql


注意,我上面说的是 用lazy修饰关联关系:hibernate不会发主动发关联的sql


为什么说主动呢?看看下面的代码


我们改一改代码6


//代码7
public void testGetPerson() {
Session s = sessionFactory.getCurrentSession();
s.beginTransaction();
Person p = (Person)s.get(Person.class, 2);
System.out.println(p.getDreams().size());
s.getTransaction().commit();
}

此时不设置person的fetch值。(保持默认的lazy)


此时发的sql语句是:


Hibernate:
select
person0_.id as id1_0_,
person0_.myname as myname1_0_
from
Person person0_
where
person0_.id=?
Hibernate:
select
dreams0_.personId as personId1_,
dreams0_.id as id1_,
dreams0_.id as id0_0_,
dreams0_.description as descript2_0_0_,
dreams0_.personId as personId0_0_
from
Dream dreams0_
where
dreams0_.personId=?

此时我们可以得到一个结论,

如果在session里,我们只是获得"一"的那一方,hibernate默认不会去取多的那一方;但是如果在session里,访问了获得的"一"里面"多"的那一方数据(就是访问了person里面的dream)。就会发关联sql。



如此一来,就有了一个比较尴尬的事了


不管我在一的那一方设不设立fetch=FetchType.eager,我在session里面获得多的那一方的时候,都是可以的。

此时,还不如不设置fetch=FetchType.eager呢,因为有的时候,我确实不需要获得多的那一方,如果一的那一方设置成eager,岂不是要多查询很多无用的数据。



再看一个例子:


//代码8
public void testGetPerson() {

testSavePerson();

Session s = sessionFactory.getCurrentSession();
s.beginTransaction();
Person p = (Person)s.get(Person.class, 2);
s.getTransaction().commit();
System.out.println(p.getDreams().size());
}

就是把获得多的那一方数据的代码放到了session的外面。


如果此时person那边还有fetch=FetchType.eager,那么一切OK


屏幕上输出:


Hibernate:
select
person0_.id as id1_1_,
person0_.myname as myname1_1_,
dreams1_.personId as personId3_,
dreams1_.id as id3_,
dreams1_.id as id0_0_,
dreams1_.description as descript2_0_0_,
dreams1_.personId as personId0_0_
from
Person person0_
left outer join
Dream dreams1_
on person0_.id=dreams1_.personId
where
person0_.id=?
2 //dream的size是2

可是如果我把fetch=FetchType.eager去掉,在运行代码8,就会报错:


<span style="color:#FF0000;">failed to lazily initialize a collection of role: com.bjsxt.hibernate.Person.dreams, no session or session was closed</span>

说的很清楚,session已经关闭了。


为什么呢?


我们知道,从person到dream,是一对多,而默认情况下,一对多的fetch是lazy的。


也就是说,正常情况下,取了pseron是不会再取dream。


我们注意代码7发出的sql,是两个。


现在我要获得多的那方的命令,跑到了sesssion的外面,也就是说在session关闭后再去数据库里运行sql,那铁定会报错喽。


我们再试试:


//代码9
public void testGetPerson() {

testSavePerson();

Session s = sessionFactory.getCurrentSession();
s.beginTransaction();
Person p = (Person)s.get(Person.class, 2);
s.getTransaction().commit();
System.out.println(p.getDreams().size());
s.getTransaction().commit();
}

此时的person的fetch还是默认的lazy。


是否会报错呢?自己试试吧。


关于取对象这部分,hibernate还是比较麻烦的,我认为最好的方法就是保持默认的情况,一旦有了问题再查api文档,或其他的资料。

那既然这样,我为什么还要写这篇博客呢?反正最好的方法就是保持默认情况嘛。

因为:

世之奇伟、瑰怪、非常之观,常在于险远,而人之所罕至焉,故非有志者不能至也。



感谢glt