Hibernate 中 FetchMode 與 FetchType

Entity:

Entity City and Hotel, One-to-Many 雙向連接.

@Entity
public class City {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    @Column(nullable = false)
    private String country;
    
    @OneToMany(mappedBy="city")
    private Set<Hotel> hotles;

    ...

}
@Entity
public class Hotel {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne(optional = false)
    @NaturalId
    private City city;

    @Column(nullable = false)
    @NaturalId
    private String name;

    @Column(nullable = false)
    private String address;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "hotel")
    private Set<Review> reviews;

    ...

}

</br>
</br>


Data:

insert into city(country, name) values ('Australia', 'Brisbane')
insert into city(country, name) values ('Canada', 'Montreal')
insert into city(country, name) values ('Australia', 'Melbourne')
insert into city(country, name) values ('Israel', 'Tel Aviv')

insert into hotel(city_id, name, address) values (1, 'Hotel_A', 'Street_A')
insert into hotel(city_id, name, address) values (1, 'Hotel_B', 'Street_A')
insert into hotel(city_id, name, address) values (1, 'Hotel_C', 'Street_C')
insert into hotel(city_id, name, address) values (2, 'Hotel_D', 'Street_D')
insert into hotel(city_id, name, address) values (2, 'Hotel_E', 'Street_E')
insert into hotel(city_id, name, address) values (3, 'Hotel_F', 'Street_F')
insert into hotel(city_id, name, address) values (3, 'Hotel_G', 'Street_G')
insert into hotel(city_id, name, address) values (3, 'Hotel_H', 'Street_H')
insert into hotel(city_id, name, address) values (3, 'Hotel_I', 'Street_I')
insert into hotel(city_id, name, address) values (3, 'Hotel_J', 'Street_J')
insert into hotel(city_id, name, address) values (4, 'Hotel_K', 'Street_K')

</br>
</br>


Code causes N+1:

criteria.list()發(fā)送一條sql獲取所有City, 循環(huán)執(zhí)行city.getHotles().size()時(shí)發(fā)送N條sql獲取Hotel

    Session session = sessionFactory.openSession();
    Transaction transaction = session.beginTransaction();
        
    Criteria criteria = session.createCriteria(City.class);
    //one sql
    List<City> cityList = criteria.list();
    for(City city : cityList){
        //N sql
        System.out.println(city.getHotles().size());
    }
        
    transaction.commit();
    session.close();

output:

2017-02-25 12:00:45.630 DEBUG 5460 --- [nio-8080-exec-5] org.hibernate.SQL                        : select this_.id as id1_0_0_, this_.country as country2_0_0_, this_.name as name3_0_0_ from City this_
Hibernate: select this_.id as id1_0_0_, this_.country as country2_0_0_, this_.name as name3_0_0_ from City this_
2017-02-25 12:00:45.632 DEBUG 5460 --- [nio-8080-exec-5] org.hibernate.SQL                        : select hotles0_.city_id as city_id4_0_0_, hotles0_.id as id1_1_0_, hotles0_.id as id1_1_1_, hotles0_.address as address2_1_1_, hotles0_.city_id as city_id4_1_1_, hotles0_.name as name3_1_1_ from Hotel hotles0_ where hotles0_.city_id=?
Hibernate: select hotles0_.city_id as city_id4_0_0_, hotles0_.id as id1_1_0_, hotles0_.id as id1_1_1_, hotles0_.address as address2_1_1_, hotles0_.city_id as city_id4_1_1_, hotles0_.name as name3_1_1_ from Hotel hotles0_ where hotles0_.city_id=?
3
2017-02-25 12:00:45.634 DEBUG 5460 --- [nio-8080-exec-5] org.hibernate.SQL                        : select hotles0_.city_id as city_id4_0_0_, hotles0_.id as id1_1_0_, hotles0_.id as id1_1_1_, hotles0_.address as address2_1_1_, hotles0_.city_id as city_id4_1_1_, hotles0_.name as name3_1_1_ from Hotel hotles0_ where hotles0_.city_id=?
Hibernate: select hotles0_.city_id as city_id4_0_0_, hotles0_.id as id1_1_0_, hotles0_.id as id1_1_1_, hotles0_.address as address2_1_1_, hotles0_.city_id as city_id4_1_1_, hotles0_.name as name3_1_1_ from Hotel hotles0_ where hotles0_.city_id=?
2
2017-02-25 12:00:45.636 DEBUG 5460 --- [nio-8080-exec-5] org.hibernate.SQL                        : select hotles0_.city_id as city_id4_0_0_, hotles0_.id as id1_1_0_, hotles0_.id as id1_1_1_, hotles0_.address as address2_1_1_, hotles0_.city_id as city_id4_1_1_, hotles0_.name as name3_1_1_ from Hotel hotles0_ where hotles0_.city_id=?
Hibernate: select hotles0_.city_id as city_id4_0_0_, hotles0_.id as id1_1_0_, hotles0_.id as id1_1_1_, hotles0_.address as address2_1_1_, hotles0_.city_id as city_id4_1_1_, hotles0_.name as name3_1_1_ from Hotel hotles0_ where hotles0_.city_id=?
5
2017-02-25 12:00:45.638 DEBUG 5460 --- [nio-8080-exec-5] org.hibernate.SQL                        : select hotles0_.city_id as city_id4_0_0_, hotles0_.id as id1_1_0_, hotles0_.id as id1_1_1_, hotles0_.address as address2_1_1_, hotles0_.city_id as city_id4_1_1_, hotles0_.name as name3_1_1_ from Hotel hotles0_ where hotles0_.city_id=?
Hibernate: select hotles0_.city_id as city_id4_0_0_, hotles0_.id as id1_1_0_, hotles0_.id as id1_1_1_, hotles0_.address as address2_1_1_, hotles0_.city_id as city_id4_1_1_, hotles0_.name as name3_1_1_ from Hotel hotles0_ where hotles0_.city_id=?
1

</br>
</br>


FetchType.LAZY and @Fetch(FetchMode.SELECT):

City中加上fetch=FetchType.LAZY@Fetch(FetchMode.SELECT), 輸出結(jié)果與上面相同,說明one-to-many默認(rèn)設(shè)置是fetch=FetchType.LAZY@Fetch(FetchMode.SELECT)
下面四種配置等效,都是N+1條sql的懶加載:

@OneToMany(mappedBy="city")
private Set<Hotel> hotles;
@OneToMany(mappedBy="city")
@Fetch(FetchMode.SELECT)
private Set<Hotel> hotles;
@OneToMany(mappedBy="city", fetch=FetchType.LAZY)
private Set<Hotel> hotles;
@OneToMany(mappedBy="city", fetch=FetchType.LAZY)
@Fetch(FetchMode.SELECT)
private Set<Hotel> hotles;

</br>
</br>


FetchType.Eager and @Fetch(FetchMode.SELECT):

City中加上fetch=FetchType.Eager@Fetch(FetchMode.SELECT)

@OneToMany(mappedBy="city", fetch=FetchType.Eager)
@Fetch(FetchMode.SELECT)
private Set<Hotel> hotles;

output:

2017-02-25 14:13:45.455 DEBUG 5800 --- [nio-8080-exec-1] org.hibernate.SQL                        : select this_.id as id1_0_0_, this_.country as country2_0_0_, this_.name as name3_0_0_ from City this_
Hibernate: select this_.id as id1_0_0_, this_.country as country2_0_0_, this_.name as name3_0_0_ from City this_
2017-02-25 14:13:45.473 DEBUG 5800 --- [nio-8080-exec-1] org.hibernate.SQL                        : select hotles0_.city_id as city_id4_0_0_, hotles0_.id as id1_1_0_, hotles0_.id as id1_1_1_, hotles0_.address as address2_1_1_, hotles0_.city_id as city_id4_1_1_, hotles0_.name as name3_1_1_ from Hotel hotles0_ where hotles0_.city_id=?
Hibernate: select hotles0_.city_id as city_id4_0_0_, hotles0_.id as id1_1_0_, hotles0_.id as id1_1_1_, hotles0_.address as address2_1_1_, hotles0_.city_id as city_id4_1_1_, hotles0_.name as name3_1_1_ from Hotel hotles0_ where hotles0_.city_id=?
2017-02-25 14:13:45.482 DEBUG 5800 --- [nio-8080-exec-1] org.hibernate.SQL                        : select hotles0_.city_id as city_id4_0_0_, hotles0_.id as id1_1_0_, hotles0_.id as id1_1_1_, hotles0_.address as address2_1_1_, hotles0_.city_id as city_id4_1_1_, hotles0_.name as name3_1_1_ from Hotel hotles0_ where hotles0_.city_id=?
Hibernate: select hotles0_.city_id as city_id4_0_0_, hotles0_.id as id1_1_0_, hotles0_.id as id1_1_1_, hotles0_.address as address2_1_1_, hotles0_.city_id as city_id4_1_1_, hotles0_.name as name3_1_1_ from Hotel hotles0_ where hotles0_.city_id=?
2017-02-25 14:13:45.485 DEBUG 5800 --- [nio-8080-exec-1] org.hibernate.SQL                        : select hotles0_.city_id as city_id4_0_0_, hotles0_.id as id1_1_0_, hotles0_.id as id1_1_1_, hotles0_.address as address2_1_1_, hotles0_.city_id as city_id4_1_1_, hotles0_.name as name3_1_1_ from Hotel hotles0_ where hotles0_.city_id=?
Hibernate: select hotles0_.city_id as city_id4_0_0_, hotles0_.id as id1_1_0_, hotles0_.id as id1_1_1_, hotles0_.address as address2_1_1_, hotles0_.city_id as city_id4_1_1_, hotles0_.name as name3_1_1_ from Hotel hotles0_ where hotles0_.city_id=?
2017-02-25 14:13:45.486 DEBUG 5800 --- [nio-8080-exec-1] org.hibernate.SQL                        : select hotles0_.city_id as city_id4_0_0_, hotles0_.id as id1_1_0_, hotles0_.id as id1_1_1_, hotles0_.address as address2_1_1_, hotles0_.city_id as city_id4_1_1_, hotles0_.name as name3_1_1_ from Hotel hotles0_ where hotles0_.city_id=?
Hibernate: select hotles0_.city_id as city_id4_0_0_, hotles0_.id as id1_1_0_, hotles0_.id as id1_1_1_, hotles0_.address as address2_1_1_, hotles0_.city_id as city_id4_1_1_, hotles0_.name as name3_1_1_ from Hotel hotles0_ where hotles0_.city_id=?
3
2
5
1

同樣是N+1條sql,不過和上面情況不同的是,N條sql會(huì)在criteria.list()時(shí)執(zhí)行
</br>
</br>


FetchMode.JOIN:

City中加上@Fetch(FetchMode.JOIN), 那么Hibernate將強(qiáng)行設(shè)置為fetch=FetchType.EAGER, 用戶設(shè)置fetch=FetchType.LAZY將不會(huì)生效.
下面四種設(shè)置等效:

@OneToMany(mappedBy="city", fetch=FetchType.EAGER)
@Fetch(FetchMode.JOIN)
private Set<Hotel> hotles;
@OneToMany(mappedBy="city", fetch=FetchType.LAZY)
@Fetch(FetchMode.JOIN)
private Set<Hotel> hotles;
@OneToMany(mappedBy="city")
@Fetch(FetchMode.JOIN)
private Set<Hotel> hotles;
@OneToMany(mappedBy="city", fetch=FetchType.EAGER)
private Set<Hotel> hotles;

output:

2017-02-25 13:17:07.583 DEBUG 2964 --- [nio-8080-exec-1] org.hibernate.SQL                        : select this_.id as id1_0_1_, this_.country as country2_0_1_, this_.name as name3_0_1_, hotles2_.city_id as city_id4_0_3_, hotles2_.id as id1_1_3_, hotles2_.id as id1_1_0_, hotles2_.address as address2_1_0_, hotles2_.city_id as city_id4_1_0_, hotles2_.name as name3_1_0_ from City this_ left outer join Hotel hotles2_ on this_.id=hotles2_.city_id
Hibernate: select this_.id as id1_0_1_, this_.country as country2_0_1_, this_.name as name3_0_1_, hotles2_.city_id as city_id4_0_3_, hotles2_.id as id1_1_3_, hotles2_.id as id1_1_0_, hotles2_.address as address2_1_0_, hotles2_.city_id as city_id4_1_0_, hotles2_.name as name3_1_0_ from City this_ left outer join Hotel hotles2_ on this_.id=hotles2_.city_id
3
3
3
2
2
5
5
5
5
5
1

從輸出可看出,在執(zhí)行criteria.list()時(shí)通過一條sql 獲取了所有的City和Hotel。
使用@Fetch(FetchMode.JOIN)需要注意的是:它在Join查詢時(shí)是Full Join, 所以會(huì)有重復(fù)City出現(xiàn)
</br>
</br>


FetchMode.SUBSELECT:

City中加上@Fetch(FetchMode.SUBSELECT), 那么Hibernate將強(qiáng)行設(shè)置為fetch=FetchType.EAGER, 用戶設(shè)置fetch=FetchType.LAZY將不會(huì)生效.
下面三種設(shè)置等效:

@OneToMany(mappedBy="city", fetch=FetchType.EAGER)
@Fetch(FetchMode.SUBSELECT)
private Set<Hotel> hotles;
@OneToMany(mappedBy="city", fetch=FetchType.LAZY)
@Fetch(FetchMode.SUBSELECT)
private Set<Hotel> hotles;
@OneToMany(mappedBy="city")
@Fetch(FetchMode.SUBSELECT)
private Set<Hotel> hotles;

output:

2017-02-25 13:45:06.089 DEBUG 4004 --- [nio-8080-exec-1] org.hibernate.SQL                        : select this_.id as id1_0_0_, this_.country as country2_0_0_, this_.name as name3_0_0_ from City this_
Hibernate: select this_.id as id1_0_0_, this_.country as country2_0_0_, this_.name as name3_0_0_ from City this_
2017-02-25 13:45:06.114 DEBUG 4004 --- [nio-8080-exec-1] org.hibernate.SQL                        : select hotles0_.city_id as city_id4_0_1_, hotles0_.id as id1_1_1_, hotles0_.id as id1_1_0_, hotles0_.address as address2_1_0_, hotles0_.city_id as city_id4_1_0_, hotles0_.name as name3_1_0_ from Hotel hotles0_ where hotles0_.city_id in (select this_.id from City this_)
Hibernate: select hotles0_.city_id as city_id4_0_1_, hotles0_.id as id1_1_1_, hotles0_.id as id1_1_0_, hotles0_.address as address2_1_0_, hotles0_.city_id as city_id4_1_0_, hotles0_.name as name3_1_0_ from Hotel hotles0_ where hotles0_.city_id in (select this_.id from City this_)
3
2
5
1

從輸出可看出,在執(zhí)行criteria.list()時(shí)通過兩條sql分別獲取City和Hotel。
</br>
</br>


Summary:

| FetchMode\FetchType | Lazy | Eager | Null |
|-----------------------------------------------------------------------------------|
| Select | A | B | A |
| Join | C | C | C |
| Subselect | D | D | D |
| Null | A | C | A |
十二種排列組合,但最后只會(huì)有四種情況,如上標(biāo)注的A,B,C,D:
A. FetchMode.SELECTFetchType.LAZY,都不設(shè)置,都設(shè)置,設(shè)置任一, 都將會(huì)是情況A。情況A會(huì)出現(xiàn)N+1條sql,其中N條是懶加載。
B. FetchMode.SELECTFetchType.EAGER同時(shí)設(shè)置時(shí)出現(xiàn)B情況,同樣是N+1條sql,和A情況不同的是N條sql會(huì)立即執(zhí)行。
C. 只要設(shè)置了FetchMode.JOIN,不管FetchType設(shè)置成什么(FetchType.EAGER,FetchType.LAZY,不設(shè)置FetchType),都將會(huì)是C情況。同時(shí),只設(shè)置FetchType.EAGER不設(shè)置FetchMode也將會(huì)是C情況。C情況下只產(chǎn)生一條立即執(zhí)行的Full-Join的sql, parent數(shù)據(jù)可能會(huì)重復(fù)。
D. 只要設(shè)置了FetchMode.SUBSELECT,不管FetchType設(shè)置成什么(FetchType.EAGER,FetchType.LAZY,不設(shè)置FetchType),都將會(huì)是D情況。D情況下產(chǎn)生兩條立即執(zhí)行的sql, 分別讀取parent表和child表。
</br>
</br>


Code:

Sample Code on Github

</br>
</br>


參考:
Hibernate FetchMode explained by example
Hibernate – fetching strategies examples
認(rèn)識(shí)與入門 Markdown
Markdown在線制表
Spring and Hibernate Application with Zero XML

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容