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.SELECT
與FetchType.LAZY
,都不設(shè)置,都設(shè)置,設(shè)置任一, 都將會(huì)是情況A。情況A會(huì)出現(xiàn)N+1條sql,其中N條是懶加載。
B. FetchMode.SELECT
與FetchType.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:
</br>
</br>
參考:
Hibernate FetchMode explained by example
Hibernate – fetching strategies examples
認(rèn)識(shí)與入門 Markdown
Markdown在線制表
Spring and Hibernate Application with Zero XML