Hibernate的優化方案
HQL優化
- 使用參數綁定
- 使用綁定參數的原因是讓數據庫一次解析SQL,對后續的重復請求可以使用生成好的執行計劃,這樣做節省CPU時間和內存。
- 避免SQL注入。
- 盡量少使用NOT 如果where子句中包含not關鍵字,那么執行時該字段的索引失效。
- 盡量使用where來替換having having在檢索出所有記錄后才對結果集進行過濾,這個處理需要一定的開銷,而where子句限制記錄的數目,能減少這方面的開銷。
- 減少對表的查詢 在含有子查詢的HQL中,盡量減少對表的查詢,降低開銷。
- 使用表的別名 當在HQL語句中連接多個表時,使用別名,提高程序閱讀性,并把別名前綴與每個列連接上,這樣一來,可以減少解析時間并減少列歧義引起的語法錯誤。
- 實體的更新與刪除 在Hibernate3以后支持hql的update與delete操作。可參考度娘。
一級緩存優化
一級緩存也叫做session緩存,在一個hibernate session有效,這級緩存的可干預性不強,大多于hibernate自動管理,但它提供清除緩存的方法,這在大批量增加(更新)操作是有效果的,例如,同時增加十萬條記錄,按常規進行,很可能會出現異常,這時可能需要手動清除一級緩存, session.evict 以及 session.clear 。
檢索策略(抓取策略)
延遲加載
延遲加載是Hibernate為提高程序執行的效率而提供的一種機制,即只有真正使用該對象的數據時才會創建。load方法采用的策略是延遲加載;get方法采用的策略是立即加載。
檢索策略分為兩種:
- 類級別檢索
- 關聯級別檢索
類級別檢索
類級別檢索是通過session直接檢索某一類對應的數據,例如:
Customer c = session.load(Customer.class, 1);
或
session.createQuery("from Order");
類級別檢索策略分為立即檢索與延遲檢索,默認是延遲檢索,類級別的檢索策略可以通過 <class> 元素的lazy屬性來設置,默認值是true。所以我們可在hbm映射配置文件中設置如下:
<class name="Customer" table="t_customer" catalog="hibernateTest" lazy="true">
...
</class>
除此之外,我們也可在PO類中使用 @Proxy 注解,例如:
@Proxy(lazy = true)
public class Customer {
...
}
現在我著重來講一下在PO類中 @Proxy 注解的使用。提示,以下所有案例代碼的編寫都是建立在 Hibernate檢索方式概述 一文案例基礎之上的。首先將 @Proxy(lazy = true) 這樣的注解加在PO類——Customer類上,這樣Customer類的代碼就變成:
// 客戶 ---- 一的一方
@Entity
@Table(name="t_customer")
@NamedQuery(name="myHql", query="from Customer")
// @SqlResultSetMapping注解才真正幫我們去規定執行sql語句如何將結果封裝到Customer對象
@SqlResultSetMapping(name="customerSetMapping",entities={ @EntityResult(entityClass=Customer.class,fields={
@FieldResult(name="id",column="id"),@FieldResult(name="name",column="name") }) })
// fields指定類里面的每一個屬性跟表中的列是如何對應的
@NamedNativeQuery(name="findCustomer",query="select * from t_customer",resultSetMapping="customerSetMapping")
// resultSetMapping需要指定一個名字,它用來指定結果如何封裝的操作
@Proxy(lazy = true)
public class Customer {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id; // 主鍵
private String name; // 姓名
// 描述客戶可以有多個訂單
/*
* targetEntity="...":相當于<one-to-many class="...">
* mappedBy="...":相當于inverse=true,即放棄關聯關系的維護,不然會生成一個中間表
*/
@OneToMany(targetEntity=Order.class,mappedBy="c")
private Set<Order> orders = new HashSet<Order>();
public Customer() {
}
public Customer(Integer id, String name) {
super();
this.id = id;
this.name = name;
}
public Set<Order> getOrders() {
return orders;
}
public void setOrders(Set<Order> orders) {
this.orders = orders;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Customer [id=" + id + ", name=" + name + "]";
}
}</code></pre>
為了便于演示延遲加載,在cn.itheima.test包下編寫一個LoadTest單元測試類,并在該類中編寫如下測試方法:
// 演示延遲加載
public class LoadTest {
@Test
public void test1() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer c1 = session.load(Customer.class, 1);
// Customer c1 = session.get(Customer.class, 1);
String name = c1.getName();
System.out.println(name);
session.getTransaction().commit();
session.close();
}
}</code></pre>
在 String name = c1.getName(); 這句代碼上加上一個斷點,然后以Debug的方式調試該程序,就能得到你想要的東西喲!!!
如果將lazy設置為false,代表類級別檢索也使用立即檢索,這時load與get方法就完全一樣了,都是立即檢索。
雖然我們是知道了load方法采用的策略是延遲加載;get方法采用的策略是立即加載,但是什么時候用get方法,什么時候用load方法呢?—— 如果你查詢的數據非常大,例如說它里面有一些大的字段,這個時候建議你采用load方法,不要一上來就立即加載,把我們的內存占滿,這樣可以讓我們的性能得到一部分的提升;如果你查詢的數據非常少,直接get就無所謂了,因為它不會占用我們很多的內存。
還有一個問題:Hibernate這個框架是在Dao層進行操作的,如果說我現在采用了一個load的方案去獲取了一個對象,我們最終會把Session關閉再返回,那么我們就要把這個對象返回到Service層,再返回到Web層,這個時候load出來的代理對象其實還沒有對數據進行初始化,也即它里面還沒有真正有數據,返回的時候就出問題了,那 如何對一個延遲的代理對象進行初始化呢? 以碼明示,在LoadTest單元測試類中編寫如下測試方法:
public class LoadTest {
// 如果對一個延遲的代理對象進行初始化?
@Test
public void test2() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer c1 = session.load(Customer.class, 1);
Hibernate.initialize(c1);
session.getTransaction().commit();
session.close();
// return c1;
}
}</code></pre>
在 Customer c1 = session.load(Customer.class, 1); 這句代碼上加上一個斷點,然后以Debug的方式調試該程序,就能得到你想要的東西喲!!!
關聯級別檢索
查詢到某個對象,獲得其關聯的對象或屬性,這種就稱為關聯級別檢索,例如:
c.getOrders().size()
c.getName()
對于關聯級別檢索我們就要研究其檢索策略(抓取策略)了。
檢索策略(抓取策略)
抓取策略介紹
抓取策略指的是查找到某個對象后,通過這個對象去查詢關聯對象的信息時的一種策略。Hibernate中對象之間的關聯關系有:
- 一對一: <one-to-one>
- 一對多(多對一): <set> 下有 <one-to-many> ,與 <many-to-one>
- 多對多: <set> 下有 <many-to-many>
此處我們主要講的是在 <set> 與 <many-to-one> 或 <one-to-one> 標簽上設置fetch、lazy這兩個屬性。
- fetch主要描述的是SQL語句的格式(例如是多條,子查詢,多表聯查)
- lazy用于控制SQL語句何時發送
例如,查詢一個客戶,要關聯查詢它的訂單。客戶代表一的一方,在客戶中有set集合來描述其訂單,在配置中我們是使用的:
<set>
<one-to-many>
</set>
此時就可以在set標簽上設置這兩個屬性fetch、lazy。
再比如,查詢一個訂單時,要查詢關聯的客戶信息。訂單代表多的一方,在訂單中有Customer對象來描述其關聯的客戶,在配置中我們是使用 <many-to-one> 標簽,此時也可以在該標簽上設置這兩個屬性fetch、lazy。當然了,也可在標簽 <one-to-one> 上設置這兩個屬性fetch、lazy。
注解配置抓取策略
以一個問題來引出該小節的講解,如何使用注解來配置抓取策略?
-
在 <set> 標簽上設置的fetch與lazy可以使用下面注解來描述:
@Fetch(FetchMode.SUBSELECT)
@LazyCollection(LazyCollectionOption.EXTRA)
private Set<Order> orders = new HashSet<Order>();
若是在映射配置文件中進行設置,則如下:

-
在 <many-to-one> 或 <one-to-one> 標簽上設置的fetch與lazy可以使用下面注解來描述:
@Fetch(FetchMode.SELECT)
@LazyToOne(LazyToOneOption.FALSE)
private Customer c; // 描述訂單屬于某一個客戶
若是在映射配置文件中進行設置,則如下:

set上的fetch與lazy
set上的fetch與lazy主要是用于設置關聯的集合信息的抓取策略。
fetch可取值有:
- SELECT:多條簡單的sql(默認值)
- JOIN:采用迫切左外連接
- SUBSELECT:將生成子查詢的SQL
lazy可取值有:
- TURE:延遲檢索(默認值)
- FALSE:立即檢索
- EXTRA:加強延遲檢索(及其懶惰)
這樣說來,fetch與lazy的組合就有九種了,其實不然,fetch與lazy的組合實際上只有七種,且聽我娓娓道來。
第一種組合
首先修改cn.itheima.domain包下的兩個PO類,如下:
-
客戶類
// 客戶 ---- 一的一方
@Entity
@Table(name="t_customer")
public class Customer {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id; // 主鍵
private String name; // 姓名
// 描述客戶可以有多個訂單
@OneToMany(targetEntity=Order.class,mappedBy="c")
@Fetch(FetchMode.SELECT)
@LazyCollection(LazyCollectionOption.TRUE)
private Set<Order> orders = new HashSet<Order>();
public Customer() {
}
public Customer(Integer id, String name) {
super();
this.id = id;
this.name = name;
}
public Set<Order> getOrders() {
return orders;
}
public void setOrders(Set<Order> orders) {
this.orders = orders;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Customer [id=" + id + ", name=" + name + "]";
}
}</code></pre> </li>
-
訂單類
// 訂單 ---- 多的一方
@Entity
@Table(name="t_order")
public class Order {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private Double money;
private String receiverInfo; // 收貨地址
// 訂單與客戶關聯
@ManyToOne(targetEntity=Customer.class)
@JoinColumn(name="c_customer_id") // 指定外鍵列
@Cascade(CascadeType.SAVE_UPDATE)
private Customer c; // 描述訂單屬于某一個客戶
public Customer getC() {
return c;
}
public void setC(Customer c) {
this.c = c;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
public String getReceiverInfo() {
return receiverInfo;
}
public void setReceiverInfo(String receiverInfo) {
this.receiverInfo = receiverInfo;
}
@Override
public String toString() {
return "Order [id=" + id + ", money=" + money + ", receiverInfo=" + receiverInfo + "]";
}
}</code></pre> </li>
</ul>
接著在cn.itheima.test包下編寫一個SetFetchTest單元測試類,并在該類中編寫如下測試方法:
public class SetFetchTest {
@Test
public void test1() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
// 1.得到id=1的Customer
Customer customer = session.get(Customer.class, 1);
// 2.得到id=1的Customer關聯的Order信息
int size = customer.getOrders().size();
System.out.println(size);
session.getTransaction().commit();
session.close();
}
}</code></pre>
在 int size = customer.getOrders().size(); 這句代碼上加上一個斷點,然后以Debug的方式調試該程序,就能得出結論:會首先查詢客戶信息,當需要訂單信息時,才會關聯查詢訂單信息,并在Eclipse控制臺打印如下sql語句:
Hibernate:
select
customer0_.id as id1_0_0_,
customer0_.name as name2_0_0_
from
t_customer customer0_
where
customer0_.id=?
Hibernate:
select
orders0_.c_customer_id as c_custom4_1_0_,
orders0_.id as id1_1_0_,
orders0_.id as id1_1_1_,
orders0_.c_customer_id as c_custom4_1_1_,
orders0_.money as money2_1_1_,
orders0_.receiverInfo as receiver3_1_1_
from
t_order orders0_
where
orders0_.c_customer_id=?
第二種組合
首先將客戶類的代碼改為:
// 客戶 ---- 一的一方
@Entity
@Table(name="t_customer")
public class Customer {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id; // 主鍵
private String name; // 姓名
// 描述客戶可以有多個訂單
@OneToMany(targetEntity=Order.class,mappedBy="c")
@Fetch(FetchMode.SELECT)
@LazyCollection(LazyCollectionOption.FALSE)
private Set<Order> orders = new HashSet<Order>();
public Customer() {
}
public Customer(Integer id, String name) {
super();
this.id = id;
this.name = name;
}
public Set<Order> getOrders() {
return orders;
}
public void setOrders(Set<Order> orders) {
this.orders = orders;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Customer [id=" + id + ", name=" + name + "]";
}
}</code></pre>
然后以Debug的方式運行SetFetchTest單元測試類中的test1方法,就能得出結論:當查詢客戶信息時,就會將訂單信息也查詢,也就是說訂單信息沒有進行延遲查詢。并在Eclipse控制臺打印如下sql語句:
Hibernate:
select
customer0_.id as id1_0_0_,
customer0_.name as name2_0_0_
from
t_customer customer0_
where
customer0_.id=?
Hibernate:
select
orders0_.c_customer_id as c_custom4_1_0_,
orders0_.id as id1_1_0_,
orders0_.id as id1_1_1_,
orders0_.c_customer_id as c_custom4_1_1_,
orders0_.money as money2_1_1_,
orders0_.receiverInfo as receiver3_1_1_
from
t_order orders0_
where
orders0_.c_customer_id=?
第三種組合
首先將客戶類的代碼改為:
// 客戶 ---- 一的一方
@Entity
@Table(name="t_customer")
public class Customer {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id; // 主鍵
private String name; // 姓名
// 描述客戶可以有多個訂單
@OneToMany(targetEntity=Order.class,mappedBy="c")
@Fetch(FetchMode.SELECT)
@LazyCollection(LazyCollectionOption.EXTRA)
private Set<Order> orders = new HashSet<Order>();
public Customer() {
}
public Customer(Integer id, String name) {
super();
this.id = id;
this.name = name;
}
public Set<Order> getOrders() {
return orders;
}
public void setOrders(Set<Order> orders) {
this.orders = orders;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Customer [id=" + id + ", name=" + name + "]";
}
}</code></pre>
然后以Debug的方式運行SetFetchTest單元測試類中的test1方法,就能得出結論:當查詢客戶信息時,不會查詢訂單信息,當需要訂單的個數時,也不會查詢訂單信息,只會通過count來統計訂單個數,當我們使用size()、contains()或isEmpty()方法時也不會查詢訂單信息。并在Eclipse控制臺打印如下sql語句:
Hibernate:
select
customer0_.id as id1_0_0_,
customer0_.name as name2_0_0_
from
t_customer customer0_
where
customer0_.id=?
Hibernate:
select
count(id)
from
t_order
where
c_customer_id =?
第四種組合
如果fetch選擇的是join方案,那么lazy它就會失效。生成SQl語句采用的是迫切左外連接(left outer join fetch),也即這個時候會多表聯查,既然是多表聯查,就會把信息都查詢出來,它既然是一個迫切左外連接,會根據你的需求把信息封裝到你指定的對象里面,所以lazy它就會失效。
為了測試這第四種組合,首先將客戶類的代碼改為:
// 客戶 ---- 一的一方
@Entity
@Table(name="t_customer")
public class Customer {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id; // 主鍵
private String name; // 姓名
// 描述客戶可以有多個訂單
@OneToMany(targetEntity=Order.class,mappedBy="c")
@Fetch(FetchMode.JOIN)
@LazyCollection(LazyCollectionOption.FALSE)
private Set<Order> orders = new HashSet<Order>();
public Customer() {
}
public Customer(Integer id, String name) {
super();
this.id = id;
this.name = name;
}
public Set<Order> getOrders() {
return orders;
}
public void setOrders(Set<Order> orders) {
this.orders = orders;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Customer [id=" + id + ", name=" + name + "]";
}
}</code></pre>
然后以Debug的方式運行SetFetchTest單元測試類中的test1方法,在Eclipse控制臺打印如下sql語句:
Hibernate:
select
customer0_.id as id1_0_0_,
customer0_.name as name2_0_0_,
orders1_.c_customer_id as c_custom4_1_1_,
orders1_.id as id1_1_1_,
orders1_.id as id1_1_2_,
orders1_.c_customer_id as c_custom4_1_2_,
orders1_.money as money2_1_2_,
orders1_.receiverInfo as receiver3_1_2_
from
t_customer customer0_
left outer join
t_order orders1_
on customer0_.id=orders1_.c_customer_id
where
customer0_.id=?
第五種組合
首先將客戶類的代碼改為:
// 客戶 ---- 一的一方
@Entity
@Table(name="t_customer")
public class Customer {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id; // 主鍵
private String name; // 姓名
// 描述客戶可以有多個訂單
@OneToMany(targetEntity=Order.class,mappedBy="c")
@Fetch(FetchMode.SUBSELECT)
@LazyCollection(LazyCollectionOption.TRUE)
private Set<Order> orders = new HashSet<Order>();
public Customer() {
}
public Customer(Integer id, String name) {
super();
this.id = id;
this.name = name;
}
public Set<Order> getOrders() {
return orders;
}
public void setOrders(Set<Order> orders) {
this.orders = orders;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Customer [id=" + id + ", name=" + name + "]";
}
}</code></pre>
接著在SetFetchTest單元測試類中編寫如下測試方法:
public class SetFetchTest {
@SuppressWarnings("unchecked")
@Test
public void test2() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
// 1.查詢出所有的客戶信息
List<Customer> list = session.createQuery("from Customer").list();
for (Customer customer : list) {
System.out.println(customer.getOrders().size());
}
session.getTransaction().commit();
session.close();
}
}</code></pre>
在 List<Customer> list = session.createQuery("from Customer").list(); 這句代碼上加上一個斷點,然后以Debug的方式調試該程序,就能得出結論:會生成子查詢,但是我們在查詢訂單時采用的是延遲加載。并在Eclipse控制臺打印如下sql語句:
Hibernate:
select
customer0_.id as id1_0_,
customer0_.name as name2_0_
from
t_customer customer0_
Hibernate:
select
orders0_.c_customer_id as c_custom4_1_1_,
orders0_.id as id1_1_1_,
orders0_.id as id1_1_0_,
orders0_.c_customer_id as c_custom4_1_0_,
orders0_.money as money2_1_0_,
orders0_.receiverInfo as receiver3_1_0_
from
t_order orders0_
where
orders0_.c_customer_id in (
select
customer0_.id
from
t_customer customer0_
)
第六種組合
首先將客戶類的代碼改為:
// 客戶 ---- 一的一方
@Entity
@Table(name="t_customer")
public class Customer {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id; // 主鍵
private String name; // 姓名
// 描述客戶可以有多個訂單
@OneToMany(targetEntity=Order.class,mappedBy="c")
@Fetch(FetchMode.SUBSELECT)
@LazyCollection(LazyCollectionOption.FALSE)
private Set<Order> orders = new HashSet<Order>();
public Customer() {
}
public Customer(Integer id, String name) {
super();
this.id = id;
this.name = name;
}
public Set<Order> getOrders() {
return orders;
}
public void setOrders(Set<Order> orders) {
this.orders = orders;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Customer [id=" + id + ", name=" + name + "]";
}
}</code></pre>
然后以Debug的方式運行SetFetchTest單元測試類中的test2方法,就能得出結論:會生成子查詢,在查詢客戶信息時,就會將訂單信息也查詢出來。并在Eclipse控制臺打印如下sql語句:
Hibernate:
select
customer0_.id as id1_0_,
customer0_.name as name2_0_
from
t_customer customer0_
Hibernate:
select
orders0_.c_customer_id as c_custom4_1_1_,
orders0_.id as id1_1_1_,
orders0_.id as id1_1_0_,
orders0_.c_customer_id as c_custom4_1_0_,
orders0_.money as money2_1_0_,
orders0_.receiverInfo as receiver3_1_0_
from
t_order orders0_
where
orders0_.c_customer_id in (
select
customer0_.id
from
t_customer customer0_
)
第七種組合
首先將客戶類的代碼改為:
// 客戶 ---- 一的一方
@Entity
@Table(name="t_customer")
public class Customer {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id; // 主鍵
private String name; // 姓名
// 描述客戶可以有多個訂單
@OneToMany(targetEntity=Order.class,mappedBy="c")
@Fetch(FetchMode.SUBSELECT)
@LazyCollection(LazyCollectionOption.EXTRA)
private Set<Order> orders = new HashSet<Order>();
public Customer() {
}
public Customer(Integer id, String name) {
super();
this.id = id;
this.name = name;
}
public Set<Order> getOrders() {
return orders;
}
public void setOrders(Set<Order> orders) {
this.orders = orders;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Customer [id=" + id + ", name=" + name + "]";
}
}</code></pre>
然后以Debug的方式運行SetFetchTest單元測試類中的test2方法,就能得出結論:在查詢訂單時,只會根據情況來確定是否要訂單信息,如果不需要,例如我們程序中的size操作,那么就會發出 select count(*) from Order where c_customer_id=? 這樣的語句。這時Eclipse控制臺會打印:
Hibernate:
select
customer0_.id as id1_0_,
customer0_.name as name2_0_
from
t_customer customer0_
Hibernate:
select
count(id)
from
t_order
where
c_customer_id =?
10
Hibernate:
select
count(id)
from
t_order
where
c_customer_id =?
10
Hibernate:
select
count(id)
from
t_order
where
c_customer_id =?
0
many-to-one或one-to-one上的fetch與lazy
set上的fetch與lazy主要是設置在獲取到代表一的一方時,如何去查詢代表多的一方。那么在 <many-to-one> 或 <one-to-one> 標簽上如何設置fetch與lazy,然后去查詢對方。對于我們的程序來說,就是在代表多的一方如何查詢代表一的一方的信息。例如,獲取到一個訂單對象,要查詢客戶信息。
fetch可取值有:
- select:默認值,代表發送一條或多條簡單的select語句
- join:發送一條迫切左外連接
lazy可取值有:
- false:不采用延遲加載
- proxy:默認值,是否采用延遲不由本方說了算,而是需要由另一方的類級別延遲策略來決定
- no-proxy:在此不討論
第一種組合
首先修改Order類的代碼為:
// 訂單 ---- 多的一方
@Entity
@Table(name="t_order")
public class Order {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private Double money;
private String receiverInfo; // 收貨地址
// 訂單與客戶關聯
@ManyToOne(targetEntity=Customer.class)
@JoinColumn(name="c_customer_id") // 指定外鍵列
@Cascade(CascadeType.SAVE_UPDATE)
@Fetch(FetchMode.SELECT)
@LazyToOne(LazyToOneOption.PROXY)
private Customer c; // 描述訂單屬于某一個客戶
public Customer getC() {
return c;
}
public void setC(Customer c) {
this.c = c;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
public String getReceiverInfo() {
return receiverInfo;
}
public void setReceiverInfo(String receiverInfo) {
this.receiverInfo = receiverInfo;
}
@Override
public String toString() {
return "Order [id=" + id + ", money=" + money + ", receiverInfo=" + receiverInfo + "]";
}
}</code></pre>
然后將Customer類的類級別延遲策略置為lazy=true,此時Customer類變為:
// 客戶 ---- 一的一方
@Entity
@Table(name="t_customer")
@Proxy(lazy=true)
public class Customer {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id; // 主鍵
private String name; // 姓名
// 描述客戶可以有多個訂單
@OneToMany(targetEntity=Order.class,mappedBy="c")
@Fetch(FetchMode.SUBSELECT)
@LazyCollection(LazyCollectionOption.EXTRA)
private Set<Order> orders = new HashSet<Order>();
public Customer() {
}
public Customer(Integer id, String name) {
super();
this.id = id;
this.name = name;
}
public Set<Order> getOrders() {
return orders;
}
public void setOrders(Set<Order> orders) {
this.orders = orders;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Customer [id=" + id + ", name=" + name + "]";
}
}</code></pre>
接著在cn.itheima.test包下編寫一個OneFetchTest單元測試類,并在該類中編寫如下測試方法:
public class OneFetchTest {
@Test
public void test1() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
// 1.得到一個訂單
Order order = session.get(Order.class, 1);
// 2.得到訂單對應的客戶
Customer c = order.getC();
System.out.println(c.getName());
session.getTransaction().commit();
session.close();
}
}</code></pre>
在 Order order = session.get(Order.class, 1); 這句代碼上加上一個斷點,然后以Debug的方式調試該程序,就能得出結論:會首先發送一條sql只查詢訂單信息,客戶信息會延遲,只有真正需要客戶信息時,才會發送sql來查詢客戶信息。并在Eclipse控制臺打印如下sql語句:
Hibernate:
select
order0_.id as id1_1_0_,
order0_.c_customer_id as c_custom4_1_0_,
order0_.money as money2_1_0_,
order0_.receiverInfo as receiver3_1_0_
from
t_order order0_
where
order0_.id=?
Hibernate:
select
customer0_.id as id1_0_0_,
customer0_.name as name2_0_0_
from
t_customer customer0_
where
customer0_.id=?
第二種組合
首先Order類的代碼不用修改,只將Customer類的類級別延遲策略置為lazy=false,即在Customer類上加上 @Proxy(lazy=false) 注解。
然后以Debug的方式運行OneFetchTest單元測試類中的test1方法,就能得出結論:當查詢訂單時,就會將客戶信息也查詢到,原因是Customer類的類級別延遲策略為false,也就是立即查詢。而且在Eclipse控制臺打印如下sql語句:
Hibernate:
select
order0_.id as id1_1_0_,
order0_.c_customer_id as c_custom4_1_0_,
order0_.money as money2_1_0_,
order0_.receiverInfo as receiver3_1_0_
from
t_order order0_
where
order0_.id=?
Hibernate:
select
customer0_.id as id1_0_0_,
customer0_.name as name2_0_0_
from
t_customer customer0_
where
customer0_.id=?
第三種組合
首先將Order類的代碼修改為:
// 訂單 ---- 多的一方
@Entity
@Table(name="t_order")
public class Order {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private Double money;
private String receiverInfo; // 收貨地址
// 訂單與客戶關聯
@ManyToOne(targetEntity=Customer.class)
@JoinColumn(name="c_customer_id") // 指定外鍵列
@Cascade(CascadeType.SAVE_UPDATE)
@Fetch(FetchMode.SELECT)
@LazyToOne(LazyToOneOption.FALSE)
private Customer c; // 描述訂單屬于某一個客戶
public Customer getC() {
return c;
}
public void setC(Customer c) {
this.c = c;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
public String getReceiverInfo() {
return receiverInfo;
}
public void setReceiverInfo(String receiverInfo) {
this.receiverInfo = receiverInfo;
}
@Override
public String toString() {
return "Order [id=" + id + ", money=" + money + ", receiverInfo=" + receiverInfo + "]";
}
}</code></pre>
然后以Debug的方式運行OneFetchTest單元測試類中的test1方法,就能得出結論:當查詢訂單時,不會對客戶信息進行延遲,會立即查詢客戶信息。而且在Eclipse控制臺打印如下sql語句:
Hibernate:
select
order0_.id as id1_1_0_,
order0_.c_customer_id as c_custom4_1_0_,
order0_.money as money2_1_0_,
order0_.receiverInfo as receiver3_1_0_
from
t_order order0_
where
order0_.id=?
Hibernate:
select
customer0_.id as id1_0_0_,
customer0_.name as name2_0_0_
from
t_customer customer0_
where
customer0_.id=?
提示:這種組合不用理Customer類的類級別延遲策略。
第四種組合
首先將Order類的代碼修改為:
// 訂單 ---- 多的一方
@Entity
@Table(name="t_order")
public class Order {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private Double money;
private String receiverInfo; // 收貨地址
// 訂單與客戶關聯
@ManyToOne(targetEntity=Customer.class)
@JoinColumn(name="c_customer_id") // 指定外鍵列
@Cascade(CascadeType.SAVE_UPDATE)
@Fetch(FetchMode.JOIN)
@LazyToOne(LazyToOneOption.FALSE)
private Customer c; // 描述訂單屬于某一個客戶
public Customer getC() {
return c;
}
public void setC(Customer c) {
this.c = c;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
public String getReceiverInfo() {
return receiverInfo;
}
public void setReceiverInfo(String receiverInfo) {
this.receiverInfo = receiverInfo;
}
@Override
public String toString() {
return "Order [id=" + id + ", money=" + money + ", receiverInfo=" + receiverInfo + "]";
}
}</code></pre>
然后以Debug的方式運行OneFetchTest單元測試類中的test1方法,就能得出結論:如果fetch的值為join,那么lazy將失效,這時會發送一條迫切左外連接來查詢,也就立即查詢。而且在Eclipse控制臺打印如下sql語句:
Hibernate:
select
order0_.id as id1_1_0_,
order0_.c_customer_id as c_custom4_1_0_,
order0_.money as money2_1_0_,
order0_.receiverInfo as receiver3_1_0_,
customer1_.id as id1_0_1_,
customer1_.name as name2_0_1_
from
t_order order0_
left outer join
t_customer customer1_
on order0_.c_customer_id=customer1_.id
where
order0_.id=?
這種組合當然也就不需要搭理Customer類的類級別延遲策略了。
批量抓取
我們在查詢多個對象的關聯對象時,可以采用批量抓取方式來對程序進行優化。要想實現批量抓取,可以在映射配置文件中通過batch-size屬性來設置,也可以使用注解 @BatchSize(size=4) 來設置,其中size表示一次抓取的條數。
先查詢客戶,然后再查詢訂單
首先檢查兩個PO類的代碼是否如下:
-
客戶類
@Entity
@Table(name="t_customer")
@Proxy(lazy=true)
public class Customer {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id; // 主鍵
private String name; // 姓名
// 描述客戶可以有多個訂單
@OneToMany(targetEntity=Order.class,mappedBy="c")
@Fetch(FetchMode.SELECT)
@LazyCollection(LazyCollectionOption.TRUE)
private Set<Order> orders = new HashSet<Order>();
public Customer() {
}
public Customer(Integer id, String name) {
super();
this.id = id;
this.name = name;
}
public Set<Order> getOrders() {
return orders;
}
public void setOrders(Set<Order> orders) {
this.orders = orders;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Customer [id=" + id + ", name=" + name + "]";
}
}</code></pre> </li>
-
訂單類
@Entity
@Table(name="t_order")
public class Order {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private Double money;
private String receiverInfo; // 收貨地址
// 訂單與客戶關聯
@ManyToOne(targetEntity=Customer.class)
@JoinColumn(name="c_customer_id") // 指定外鍵列
@Cascade(CascadeType.SAVE_UPDATE)
private Customer c; // 描述訂單屬于某一個客戶
public Customer getC() {
return c;
}
public void setC(Customer c) {
this.c = c;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
public String getReceiverInfo() {
return receiverInfo;
}
public void setReceiverInfo(String receiverInfo) {
this.receiverInfo = receiverInfo;
}
@Override
public String toString() {
return "Order [id=" + id + ", money=" + money + ", receiverInfo=" + receiverInfo + "]";
}
}</code></pre> </li>
</ul>
為了查詢出所有用戶的訂單信息,我在cn.itheima.test包下編寫一個BatchFetchTest單元測試類,并在該類中編寫如下測試方法:
// 演示批量抓取
public class BatchFetchTest {
// 查詢出所有用戶的訂單信息
@Test
public void test1() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
// 1.得到所有客戶
List<Customer> list = session.createQuery("from Customer").list();
// 2.得到客戶的訂單信息
for (Customer customer : list) {
System.out.println(customer.getOrders().size());
}
session.getTransaction().commit();
session.close();
}
}</code></pre>
運行以上方法,可發現Eclipse控制臺打印如下:
Hibernate:
select
customer0_.id as id1_0_,
customer0_.name as name2_0_
from
t_customer customer0_
Hibernate:
select
orders0_.c_customer_id as c_custom4_1_0_,
orders0_.id as id1_1_0_,
orders0_.id as id1_1_1_,
orders0_.c_customer_id as c_custom4_1_1_,
orders0_.money as money2_1_1_,
orders0_.receiverInfo as receiver3_1_1_
from
t_order orders0_
where
orders0_.c_customer_id=?
10
Hibernate:
select
orders0_.c_customer_id as c_custom4_1_0_,
orders0_.id as id1_1_0_,
orders0_.id as id1_1_1_,
orders0_.c_customer_id as c_custom4_1_1_,
orders0_.money as money2_1_1_,
orders0_.receiverInfo as receiver3_1_1_
from
t_order orders0_
where
orders0_.c_customer_id=?
10
Hibernate:
select
orders0_.c_customer_id as c_custom4_1_0_,
orders0_.id as id1_1_0_,
orders0_.id as id1_1_1_,
orders0_.c_customer_id as c_custom4_1_1_,
orders0_.money as money2_1_1_,
orders0_.receiverInfo as receiver3_1_1_
from
t_order orders0_
where
orders0_.c_customer_id=?
0
上述代碼操作,當我們執行時,首先發出一條sql來查詢所有客戶信息,然后根據客戶的id來查詢訂單信息,因為有三個客戶,所以發送了三條sql,完成了查詢訂單信息的操作。以上一共執行了四條sql語句來完成操作,這就引出了一個N+1的經典問題。這里,就 可以采用批量抓取來解決N+1問題。
我們不僅可以在客戶類映射配置文件中的 <set> 標簽上配置batch-size,如下:

而且也可使用注解 @BatchSize(size=3) 來進行配置,即需要在Customer類中的 orders 屬性上加上 @BatchSize(size=3) 注解。
這樣再次運行test1方法,就可發現Eclipse控制臺打印如下:

提示:size的值要根據你當前的環境來設置,但是它的值不要太大,最好不要超過50。
先查詢訂單,然后再查詢客戶
為了查詢出所有的訂單,然后根據訂單再查詢出客戶信息,我在BatchFetchTest單元測試類再編寫如下測試方法:
public class BatchFetchTest {
// 查詢出所有的訂單,然后根據訂單再查詢出客戶信息
@Test
public void test2() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
// 1.得到所有訂單
List<Order> list = session.createQuery("from Order").list();
// 2.得到客戶信息
for (Order order : list) {
System.out.println(order.getC().getName());
}
session.getTransaction().commit();
session.close();
}
}</code></pre>
運行以上方法,可發現Eclipse控制臺打印如下:
Hibernate:
select
order0_.id as id1_1_,
order0_.c_customer_id as c_custom4_1_,
order0_.money as money2_1_,
order0_.receiverInfo as receiver3_1_
from
t_order order0_
Hibernate:
select
customer0_.id as id1_0_0_,
customer0_.name as name2_0_0_
from
t_customer customer0_
where
customer0_.id=?
Hibernate:
select
customer0_.id as id1_0_0_,
customer0_.name as name2_0_0_
from
t_customer customer0_
where
customer0_.id=?
訂單一共有兩種,在查詢時會首先發送一條sql查詢出所有訂單,然后再根據訂單查詢出所有客戶,一共3條語句完成。這時也出現同樣的N+1問題,當然也可以采用批量抓取來解決這個N+1問題。
注意:訂單與客戶,客戶它是一個主表,訂單是一個從表。在設置批量抓取時都是在主表中設置。故我們不僅可以在客戶類映射配置文件中的 <class> 標簽上配置batch-size,如下:

而且也可使用注解 @BatchSize(size=10) 來進行配置,即需要在Customer類上加上 @BatchSize(size=10) 注解。
這樣再次運行test2方法,就可發現Eclipse控制臺打印如下:

總結
無論是根據哪一方來查詢另一方,在進行批量抓取時,都是在父方設置。如果是要查詢子方信息,那么我們是在父方那個映射配置文件的 <set> 標簽上來設置batch-size屬性,如果是從子方來查詢父方,也是在父方那個映射配置文件的 <class> 標簽上設置batch-size屬性。
父方與子方的區分:有外鍵的表是子方(從表),關聯方就是父方(主表)。
來自:http://blog.csdn.net/yerenyuan_pku/article/details/70768603