1. 程式人生 > >(轉) Hibernate持久化類與主鍵生成策略

(轉) Hibernate持久化類與主鍵生成策略

bject 規則 修飾符 cti arc arch 斷點 可能 策略

http://blog.csdn.net/yerenyuan_pku/article/details/65462930

Hibernate持久化類

什麽是持久化類呢?在Hibernate中持久化類的英文名稱是Persistent Object(簡稱PO),PO=POJO+hbm映射配置文件。
對於Hibernate中的PO,有如下編寫規則:

  1. 必須提供一個無參數的public構造方法。
  2. 所有屬性要用private修飾,對外提供public的get/set方法。
  3. 在PO類必須提供一個標識屬性,讓它與數據庫中的主鍵對應,我們管這個屬性叫OID。
  4. PO類中的屬性盡量使用基本數據類型的包裝類。
  5. PO類不能使用final修飾符。

對於第1、2點,勿須多言,下面我著重解釋一下後面3點。

為何PO類必須提供一個標識屬性OID,讓它與數據庫中的主鍵對應呢?

OID指的是與數據庫中表的主鍵對應的屬性。Hibernate框架是通過OID來區分不同的PO對象,如果在內存中有兩個相同的OID對象,那麽Hibernate認為它們是同一個對象。大家理解起來不是很好理解,它涉及到關於Hibernate緩存的概念,因為Hibernate是對數據庫直接操作,那麽我們為了優化它呢,肯定提供一些緩存的策略。那麽在緩存裏面我們怎麽知道這個對象重不重復呢?我們是通過OID來區分的。

為何PO類中的屬性應盡量使用基本數據類型的包裝類?

使用基本數據類型是沒有辦法去描述不存在的概念的,如果使用包裝類型,它就是一個對象,對於對象它的默認值是null,我們知道如果它為null,就代表不存在,那麽它就可以幫助我們去描述不存在的概念。

為何PO類不能使用final修飾符?

要回答這個問題,必須要知道Hibernate中的get/load方法的區別。這也是Hibernate中常考的面試題。我先給出答案:

雖然get/load方法它們都是根據id去查詢對象,但他倆的區別還是蠻大的:
1. get方法直接得到一個持久化類型對象,它就是立即查詢操作,也即我要什麽就查到什麽。load方法它得到的是持久化類的代理類型對象(子類對象)。它采用了一種延遲策略來查詢數據。這時如果PO類使用final修飾符,就會報錯,因為final修飾的類不可以被繼承。

2. get方法在查詢時,如果不存在返回null;load方法在查詢時,如果不存在,會產生異常——org.hibernate.ObjectNotFoundException。

現在就來編程釋疑以上這段話,首先我們要搭好Hibernate的開發環境,讀過我前面文章的童鞋,應該可以快速搭建好的,在此不做過多贅述。
在cn.itheima.test包下新建一個單元測試類——HibernateTest.java,我們首先測試Hibernate中的get()方法。

public class HibernateTest {

    // 測試get/load方法的區別
    @Test
    public void test1() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        // 操作
        Customer customer = session.get(Customer.class, 3);
        System.out.println(customer.getClass()); // cn.itheima.domain.Customer

        session.getTransaction().commit();
        session.close();
    }
}

測試test1()方法,可以發現Eclipse控制臺打印:

class cn.itheima.domain.Customer

這已說明get()方法直接得到是一個持久化類型對象。
再將get方法改置為load方法,可以發現Eclipse控制臺打印:

class cn.itheima.domain.Customer_$$_jvstd48_0

這似乎說明了load方法得到的是持久化類的代理類型對象(即子類對象)。
現在在這一行上加上一個斷點:

Customer customer = session.get(Customer.class, 3);

然後以斷點模式運行test1()方法,可發現get方法是立即查詢,也即我要什麽就查到什麽。
再將get方法改置為load方法,以斷點模式運行test1()方法,可發現只有當我們訪問對象的get方法時才向數據庫發送select語句。這已然說明它采用了一種延遲策略來查詢數據。
數據庫中的t_customer表中顯然是沒有id=100的客戶的,而我們就是要查詢這個客戶,可分別試試get/load()方法。這裏也是先測試Hibernate中的get()方法。

public class HibernateTest {

    // 測試get/load方法的區別
    @Test
    public void test1() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        // 操作
        Customer customer = session.get(Customer.class, 100);
        System.out.println(customer);

        session.getTransaction().commit();
        session.close();
    }
}

測試test1()方法,可以發現Eclipse控制臺打印null,這就說明了get方法在查詢時,如果不存在則返回null
再將get方法改置為load方法,可發現報如下異常:
技術分享
這已然說明了load方法在查詢時,如果不存在,會產生異常org.hibernate.ObjectNotFoundException

Hibernate主鍵生成策略

定義hbm.xml映射文件和pojo類時都需要定義主鍵,Hibernate中定義的主鍵類型包括自然主鍵和代理主鍵:

  • 自然主鍵(業務主鍵)
    具有業務含義的字段作為主鍵,比如:學號、身份證號。
  • 代理主鍵(邏輯主鍵)
    不具有業務含義的字段作為主鍵(例如自增id),比如:mysql自增主鍵,oracle序列生成的主鍵、uuid()方法生成的唯一序列串。

建議:企業開發中使用代理主鍵!

主鍵生成器描述
increment 代理主鍵。由Hibernate維護一個變量,每次生成主鍵時自動以遞增。問題:如果有多個應用訪問一個數據庫,由於每個應用維護自己的主鍵,所以此時主鍵可能沖突。建議不采用。優點:可以方便跨數據庫平臺。缺點:不適合高並發訪問。
identity 代理主鍵。由底層數據庫生成標識符。條件是數據庫支持自動增長數據類型。比如:mysql的自增主鍵,oracle不支持主鍵自動生成。如果數據庫支持自增建議采用。優點:由底層數據庫維護,和Hibernate無關。缺點:只能對支持自動增長的數據庫有效,例如mysql。
sequence 代理主鍵。Hibernate根據底層數據庫序列生成標識符。條件是數據庫支持序列,比如oracle的序列。如果數據庫支持序列建議采用。優點:由底層數據庫維護,和Hibernate無關。缺點:數據庫必須支持sequence方案,例如oracle。
native 代理主鍵。根據底層數據庫自動選擇identity、sequence、hilo,由於生成主鍵策略的控制權由Hibernate控制,所以不建議采用。優點:在項目中如果存在多個數據庫時使用。缺點:效率比較低。
uuid 代理主鍵。Hibernate采用128位的UUID算法來生成標識符。該算法能夠在網絡環境中生成唯一的字符串標識符。此策略可以保證生成主鍵的唯一性,並且提供了最好的數據庫插入性能和數據庫平臺的無關性。建議采用。優點:與數據庫無關,方便數據庫移植,效率高,不訪問數據庫就可以直接生成主鍵值,並且它能保證唯一性。缺點:uuid長度大(32位十六進制數),占用空間比較大,對應數據庫中char/varchar類型。
assigned 自然主鍵。由java程序負責生成標識符。不建議采用。盡量在操作中避免手動對主鍵操作。

了解上面的知識之後,我來告訴大家怎麽來配置。

  • increment

    <id name="id" column="id" type="int"> <!-- java數據類型 -->
        <!-- 主鍵生成策略 -->
        <generator class="increment"></generator>
    </id>
  • identity

    <id name="id" column="id" type="int"> <!-- java數據類型 -->
        <!-- 主鍵生成策略 -->
        <generator class="identity"></generator>
    </id>
  • sequence

    <id name="id" column="id" type="int"> <!-- java數據類型 -->
        <!-- 主鍵生成策略 -->
        <generator class="sequence"></generator>
    </id>

    如果這樣配置,則默認使用的序列是hibernate_id。但你也可以給其指定一個序列,比如說在Oracle數據庫裏面手動創建了一個序列,在Oracle數據庫中創建一個序列的語法:create sequence 序列名稱; ,則這時就該這麽配置:

    <id name="id" column="id" type="int"> <!-- java數據類型 -->
        <!-- 主鍵生成策略 -->
        <generator class="sequence">
            <param name="sequence">序列名稱</param>
        </generator>
    </id>
    • 6
  • native

    <id name="id" column="id" type="int"> <!-- java數據類型 -->
        <!-- 主鍵生成策略 -->
        <generator class="native"></generator>
    </id>
  • uuid

    <id name="id" column="id" type="string"> <!-- java數據類型 -->
        <!-- 主鍵生成策略 -->
        <generator class="uuid"></generator>
    </id>

    註意:主鍵的type應是string。

  • assigned

    <id name="id" column="id" type="int"> <!-- java數據類型 -->
        <!-- 主鍵生成策略 -->
        <generator class="assigned"></generator>
    </id>

    註意:盡量在操作中避免手動對主鍵操作。

持久化對象的三種狀態

Hibernate中持久化對象有三種狀態:

  1. 瞬時態:也叫做臨時態或自由態,它一般指我們new出來的對象,它不存在OID,與Hibernate Session無關聯,在數據庫中也無記錄。它使用完成後,會被JVM直接回收掉,它只是用於信息攜帶。
    簡單說:無OID且與數據庫中的信息無關聯,不在Session管理範圍內。
  2. 持久態:在Hibernate Session管理範圍內,它具有持久化標識OID。它的特點是在事務未提交前一直是持久態,當它發生改變時,Hibernate是可以檢測到的。
    簡單說:有OID且由Session管理,在數據庫中有可能有,也有可能沒有。
  3. 脫管態:也叫做遊離態或離線態,它是指持久態對象失去了與Session的關聯,脫管態對象它存在OID,在數據庫中有可能存在,也有可能不存在。對於脫管態對象,它發生改變時Hibernet不能檢測到。

接著來測試一下持久化對象的三種狀態,代碼如下:

public class HibernateTest {

    // 測試持久化對象的三種狀態
    @Test
    public void test2() {
        // 1.得到session
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        Customer c = new Customer(); // 瞬時態(無OID,與session無關聯)
        c.setName("張三");
        c.setSex("男");

        session.save(c); // 建立c與session的關聯關系,它就是持久態的了(有OID)

        // 2.事務提交,並關閉session
        session.getTransaction().commit();
        session.close();

        System.out.println(c.getId()); // 斷開了與session的關聯,它就是脫管態的了(有OID)
    }
}

持久化類三種狀態之間的切換

判斷持久化類對象三種狀態的依據:

  1. 是否有OID
  2. 判斷是否與Session關聯

持久化類對象三種狀態之間的切換可參考下圖:
技術分享
我稍微做一下解釋:

  1. 瞬時態(new出來的)
    瞬時→持久:save()、saveOrUpdate()方法
    瞬時→脫管(遊離):可手動設置oid,但不建議這麽做。如下:

    public class HibernateTest {
    
        // 測試持久化對象的三種狀態
        @Test
        public void test2() {
            // 1.得到session
            Session session = HibernateUtils.openSession();
            session.beginTransaction();
    
            Customer c = new Customer(); // 瞬時態(無OID,與session無關聯)
            c.setName("張三");
            c.setSex("男");
    
            c.setId(7); // 瞬時→脫管(遊離)
            System.out.println(c.getId());
        }
    }
  2. 持久態,它是由Session管理。
    持久→瞬時:delete()——這麽操作以後相當於數據庫裏面就沒有這個記錄了,被刪除後的持久化對象不在建議使用了。
    持久→脫管:註意Session本身是有緩存的,它的緩存就是所說的一級緩存。

    • evict:清除一級緩存中指定的一個對象
    • clear:清空一級緩存
    • close:關閉,也即清空一級緩存
  3. 脫管態(我們要知道它是無法直接獲取的)
    脫管→瞬時:直接將oid刪除(不建議這麽做,因為我們不建議操作脫管態的對象)。如:

    public class HibernateTest {
    
        // 測試持久化對象的三種狀態
        @Test
        public void test2() {
            // 1.得到session
            Session session = HibernateUtils.openSession();
            session.beginTransaction();
    
            Customer c = new Customer(); // 瞬時態(無OID,與session無關聯)
            c.setName("張三");
            c.setSex("男");
    
            c.setId(7); // 瞬時→脫管(遊離)
            c.setId(null); // 脫管(遊離)→瞬時
            System.out.println(c.getId());
        }
    }

    脫管→持久:update、saveOrUpdate、lock(過時),也是不建議這麽做。

(轉) Hibernate持久化類與主鍵生成策略