1. 程式人生 > >Hibernate 檢索策略的學習

Hibernate 檢索策略的學習

檢索資料也就是查詢資料的時候存在兩個問題:
1.不浪費記憶體:例如,Customer和Order是雙向1-N的關係。當 Hibernate 從資料庫中載入 Customer 物件時, 如果同時載入所有關聯的 Order 物件, 而程式實際上僅僅需要訪問 Customer 物件, 那麼這些關聯的 Order 物件就白白浪費了許多記憶體。
2.更高的查詢效率:傳送儘可能少的 SQL 語句。

延伸:Session的get()方法與load()方法的比較
1.都是按照OID查詢指定物件
2.記錄不存在的處理方式:get返回null, load()丟擲HiberbateException異常
3.get()直接返回持久化物件,總是立即載入
load()支援延遲載入

一.檢索策略

1.分類:

立即檢索:
立即載入與當前物件關聯的物件,需要執行多條SQL語句
延遲檢索:
不立即載入與當前物件關聯的物件,當第一次訪問關聯物件的時候才載入其資訊
迫切左外連線檢索:
通過左外連線載入與當前物件關聯的物件,為立即檢索策略,但是執行的sql語句少,只執行一條select連線查詢語句

2.檢索的方法

通過Session的get和load方法載入指定的OID的物件
通過HQL,QBC等檢索方法載入滿足條件的物件

3.檢索的作用於包括:

類級別
作用於當前物件,設定對當前物件是立即檢索還是延遲檢索。預設是延遲檢索,只對load()有效


關聯級別
設定對關聯物件是立即檢索,延遲檢索還是迫切左外連線檢索,預設是延遲檢索

二.類級別的檢索策略

類級別的檢索策略可以通過 元素的 lazy 屬性進行設定

如果程式載入一個物件的目的是為了訪問它的屬性,可以採取立即檢索;如果程式載入一個持久化物件的目的是僅僅為了獲得它的引用,可以採用延遲檢索,但需要注意懶載入異常(LazyInitializationException:簡單理解該異常就是Hibernate在使用延遲載入時,並沒有將資料實際查詢出來,而只是得到了一個代理物件,當使用屬性的時候才會去查詢,而如果這個時候session關閉了,則會報該異常)!
下面通過一個例子來進行講解:
在該Demo中,我們只需要使用一個Customer的物件即可,其中包含了id,name等屬性。
延遲檢索


首先我們來測試一下元素的lazy屬性為true的情況,也就是預設情況(不設定)。

public class HibernateTest {

    private SessionFactory sessionFactory;
    private Session session;
    private Transaction transaction;
    @Before
    public void init() {
        Configuration configuration = new Configuration().configure();
        ServiceRegistry serviceRegistry = new ServiceRegistryBuilder()
                .applySettings(configuration.getProperties())
                .buildServiceRegistry();
        sessionFactory = configuration.buildSessionFactory(serviceRegistry);

        session = sessionFactory.openSession();
        transaction = session.beginTransaction();
    }
    @After
    public void destroy() {
        transaction.commit();
        session.close();
        sessionFactory.close();
    }
    @Test
    public void testClassLevelStrategy() {
        Customer customer = (Customer) session.load(Customer.class, 1);
        System.out.println(customer.getClass());
        System.out.println(customer.getCustomerId());
        System.out.println(customer.getCustomerName());
    }
}

看一下上面的程式碼,該程式碼是利用Junit進行測試(關於Junit的知識在這不多說,並不是重點)。其中init方法是對SessionFactory、Session等進行初始化,destroy方法是進行關閉。

當我們執行testClassLevelStrategy()方法時,會得到以下輸出結果:

class com.atguigu.hibernate.strategy.Customer_$$_javassist_1
1
Hibernate:
    select
        customer0_.CUSTOMER_ID as CUSTOMER1_0_0_,
        customer0_.CUSTOMER_NAME as CUSTOMER2_0_0_
    from
        CUSTOMERS customer0_
    where
        customer0_.CUSTOMER_ID=?
AA

通過控制檯的輸出結果,我們可以清楚的看到,當我們執行session.load(Customer.class, 1)方法時,Hibernate並沒有傳送SQL語句,而只是返回了一個物件,通過呼叫getClass()方法,可以看到該物件class com.atguigu.hibernate.strategy.Customer_$$_javassist_1是一個代理物件,並且當呼叫customer.getCustomerId()獲取ID的時候,也沒有傳送SQL語句;當我們這個再呼叫customer.getCustomerName()方法來得到name的時候,這個時候才傳送了SQL語句進行真正的查詢,並且WHERE條件中帶上的就是ID。

如果我們在System.out.println(customer.getCustomerName());語句前插入session.close()將Session關閉,就能看到之前上文中提到的懶載入異常。

立即檢索
為了讓Customer類可以立即檢索,我們要修改Customer.hbm.xml檔案,在標籤中加入lazy=”false”。

<hibernate-mapping package="com.atguigu.hibernate.strategy">

    <class name="Customer" table="CUSTOMERS" lazy="false">

        <id name="customerId" type="java.lang.Integer">
            <column name="CUSTOMER_ID" />
            <generator class="native" />
        </id>

這個時候,我們再執行之前的單元測試程式碼,控制檯會得到以下輸出結果:

Hibernate:
    select
        customer0_.CUSTOMER_ID as CUSTOMER1_0_0_,
        customer0_.CUSTOMER_NAME as CUSTOMER2_0_0_
    from
        CUSTOMERS customer0_
    where
        customer0_.CUSTOMER_ID=?
class com.atguigu.hibernate.strategy.Customer
1
AA

我們可以看到,當呼叫load方法時,會發送SQL語句,並且得到的不再是代理物件。這個時候就算我們在輸出name屬性之前將session關閉,也不會報錯。

小結
上文中對Hibernate的類級別的檢索策略進行了總結,當然這些是建立在有一定基礎的前提下。需要注意的是:

-無論元素的lazy 屬性是true 還是false,Session 的get() 方法及Query 的list() 方法在類級別總是使用立即檢索策略。
-若 元素的 lazy 屬性為 true 或取預設值, Session 的 load() 方法不會執行查詢資料表的 SELECT 語句,僅返回代理類物件的例項,該代理類例項有如下特徵:
由 Hibernate 在執行時採用 CGLIB 工具動態生成;
Hibernate 建立代理類例項時,僅初始化其 OID 屬性;
在應用程式第一次訪問代理類例項的非 OID 屬性時, Hibernate 會初始化代理類例項。

三.關聯級別的檢索策略

1.一多關聯檢索策略

我們在對映檔案中,用元素來配置1-N關聯以及N-N關聯關係。元素有lazy、fetch和batch-size屬性。
lazy: 主要決定orders 集合被初始化的時機。即到底是在載入Customer 物件時就被初始化, 還是在程式訪問 orders 集合時被初始化。
fetch: 取值為 “select” 或 “subselect” 時, 決定初始化 orders 的查詢語句的形式;若取值為”join”, 則決定 orders 集合被初始化的時機
若把 fetch 設定為 “join”, lazy 屬性將被忽略
batch-size 屬性:用來為延遲檢索策略或立即檢索策略設定批量檢索的數量. 批量檢索能減少 SELECT 語句的數目, 提高延遲檢索或立即檢索的執行效能。

鑑於一多關聯關係使用較多,這裡以一多關係來講解關聯級別的檢索策略。
以Grade(班級)和Student的關係為例:

List<Grade> list=session.createQuery("from Grade").list();//1

延遲檢索時
將延載入當前物件的關聯物件,班級類的對映檔案配置如下:

<set name="students" cascade="save-update" inverse="true" lazy="true">

注:lazy=”true”可以不寫,因為預設的就是true
此時執行第一行程式碼的時候,將產生一條SQL語句。只查詢班級資訊,而不查詢關聯的學生的資訊:

select * from GRADE

立即檢索
將載入當前物件的關聯物件,班級類的對映檔案如下:

<set name="students" cascade="save-update" inverse="true" lazy="false">

此時在執行第一行程式碼的時候,如果有n個班級資訊,將會產生n+1個sql語句,此時不僅載入班級資訊,還載入與班級關聯的學生資訊:

select * from GRADE
select * from STUDENT s where s.GID=110701
select * from STUDENT s where s.GID=110702

要解決n條資訊,產生n+1條SQL語句的問題,可以考慮設定兩個屬性:batch-size,fetch;

2.配置batch-size

以Grade(班級)和Student的關係為例:

List<Grade> list=session.createQuery("from Grade").list();//1

通過設定batch-size批量檢索的數量,來減少select語句的數量,減少訪問資料庫的次數,提高檢索的效能。
班級類的對映配置檔案改成如下:

< set name="students" cascade="save-update" inverse="true" lazy="false" batch-size="3">

batch-size預設的值是1,合理取值是2-10

3.配置fetch

fetch關鍵字表示載入關聯物件查詢語句的形式,以及載入關聯物件的事件,取值如下:
select:載入關聯物件的時候通過select語句實現,預設值
subselect:載入關聯物件的時候通過帶子查詢語句實現
jion:採用迫切左外連線檢索所有關聯物件。‘

其中select和subselect適用於立即檢索和延遲檢索,join僅適用於立即檢索。
select和subselect決定載入關聯物件時候查詢語句的形式,join還決定了載入關聯物件的時機。
注意:
fetch=”join”僅適用於立即查詢,lazy屬性將被忽略,但是對list()方法不起作用