Hibernate的優化方案

IamHero 7年前發布 | 37K 次閱讀 Hibernate 數據庫 持久層框架

HQL優化

  • 使用參數綁定
    1. 使用綁定參數的原因是讓數據庫一次解析SQL,對后續的重復請求可以使用生成好的執行計劃,這樣做節省CPU時間和內存。
    2. 避免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方法采用的策略是立即加載。

檢索策略分為兩種:

  1. 類級別檢索
  2. 關聯級別檢索

類級別檢索

類級別檢索是通過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中對象之間的關聯關系有:

  1. 一對一: <one-to-one>
  2. 一對多(多對一): <set> 下有 <one-to-many> ,與 <many-to-one>
  3. 多對多: <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可取值有:

  1. SELECT:多條簡單的sql(默認值)
  2. JOIN:采用迫切左外連接
  3. SUBSELECT:將生成子查詢的SQL

lazy可取值有:

  1. TURE:延遲檢索(默認值)
  2. FALSE:立即檢索
  3. 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可取值有:

    1. select:默認值,代表發送一條或多條簡單的select語句
    2. join:發送一條迫切左外連接

    lazy可取值有:

    1. false:不采用延遲加載
    2. proxy:默認值,是否采用延遲不由本方說了算,而是需要由另一方的類級別延遲策略來決定
    3. 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

       

 本文由用戶 IamHero 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!