本篇博客是之前博客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

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