(轉) Hibernate持久化類與主鍵生成策略
http://blog.csdn.net/yerenyuan_pku/article/details/65462930
Hibernate持久化類
什麽是持久化類呢?在Hibernate中持久化類的英文名稱是Persistent Object(簡稱PO),PO=POJO+hbm映射配置文件。
對於Hibernate中的PO,有如下編寫規則:
- 必須提供一個無參數的public構造方法。
- 所有屬性要用private修飾,對外提供public的get/set方法。
- 在PO類必須提供一個標識屬性,讓它與數據庫中的主鍵對應,我們管這個屬性叫OID。
- PO類中的屬性盡量使用基本數據類型的包裝類。
- 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中持久化對象有三種狀態:
- 瞬時態:也叫做臨時態或自由態,它一般指我們new出來的對象,它不存在OID,與Hibernate Session無關聯,在數據庫中也無記錄。它使用完成後,會被JVM直接回收掉,它只是用於信息攜帶。
簡單說:無OID且與數據庫中的信息無關聯,不在Session管理範圍內。 - 持久態:在Hibernate Session管理範圍內,它具有持久化標識OID。它的特點是在事務未提交前一直是持久態,當它發生改變時,Hibernate是可以檢測到的。
簡單說:有OID且由Session管理,在數據庫中有可能有,也有可能沒有。 - 脫管態:也叫做遊離態或離線態,它是指持久態對象失去了與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)
}
}
持久化類三種狀態之間的切換
判斷持久化類對象三種狀態的依據:
- 是否有OID
- 判斷是否與Session關聯
持久化類對象三種狀態之間的切換可參考下圖:
我稍微做一下解釋:
-
瞬時態(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()); } }
-
持久態,它是由Session管理。
持久→瞬時:delete()——這麽操作以後相當於數據庫裏面就沒有這個記錄了,被刪除後的持久化對象不在建議使用了。
持久→脫管:註意Session本身是有緩存的,它的緩存就是所說的一級緩存。- evict:清除一級緩存中指定的一個對象
- clear:清空一級緩存
- close:關閉,也即清空一級緩存
-
脫管態(我們要知道它是無法直接獲取的)
脫管→瞬時:直接將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持久化類與主鍵生成策略