1. 程式人生 > >Hibernate入門之主鍵生成策略詳解

Hibernate入門之主鍵生成策略詳解

前言

上一節我們講解了Hibernate命名策略,從本節我們開始陸續講解屬性、關係等對映,本節我們來講講主鍵的生成策略。

主鍵生成策略

JPA規範支援4種不同的主鍵生成策略(AUTO、IDENTITY、SEQUENCE、TABLE),這些策略以程式設計方式生成主鍵值或使用資料庫功能(例如自動遞增或序列),我們只需將@GeneratedValue註解新增到主鍵屬性上並選擇對應的生成策略。

GenerationType.AUTO

它是預設的生成策略,並允許永續性提供程式選擇生成策略,如果使用Hibernate作為永續性框架,它將基於資料庫特定的Dialect選擇生成策略,對於大多數流行的關係資料庫,它會選擇GenerationType.SEQUENCE生成策略。

@Entity
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;
}

 

 

此時將生成預設名稱為hibernate_sequence的序列表,該序列表只有名為next_val的一列,該列儲存的是下一個主鍵值。也就是說當在對應表中計劃新增第一行資料時,此時會向序列表中插入一行資料即next_val等於1,為了資料一致性,然後查詢出該next_val值並新增排他鎖即(for update),同時更新該next_val等於2,最後向對應表中的主鍵設定設定為查詢出來的next_val值。整個過程生成的SQL語句如下:

insert into hibernate_sequence values ( 1 )

select next_val as id_val from hibernate_sequence for update

update hibernate_sequence set next_val= ? where next_val=?

insert into Student (email, firstName, lastName, id) values (?, ?, ?, ?)

GenerationType.SEQUENCE

它是使用資料庫序列生成唯一值的方法,它需要其他select語句才能從資料庫序列中獲取下一個值,但這對大多數應用程式沒有效能影響。如果應用程式必須保留大量的新實體,則可以使用某些特定於Hibernate的優化來減少語句的數量。

@Entity
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private int id;
}

通過上述我們知道預設生成的序列表名稱為hibernate_sequence,當我們開啟會話插入5條資料時,此時序列表中的next_val為6,也就說序列表中的序列Id和表中主鍵自增的順序一致,如下:

針對主鍵通過序列號生成的策略還有一個註解@SequenceGenerator,我們進行如下配置後,此時將生成名為student_seq的序列表。

@Entity
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "student_generator")
    @SequenceGenerator(name = "student_generator", sequenceName = "student_seq")
    private int id;
}

針對@GenerateValue註解,我們知道在預設情況下將會生成名為的hibernate_sequence的序列表且此時對應表的主鍵自增和序列表中列next_val一致,同時上述我們新增對生成序列號的註解@SequenceGenerator後,此時next_val將為101,這是因為在該註解上有一個名為allocationSize的屬性且預設值為50(可修改為負數)。但是若我們去掉該註解,在註解@GeneratedValue上有一個名為generator的屬性,我們進行如下顯式配置,結果將和上述使用註解@SequenceGenerator後的結果一致。

@Entity
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "student_seq")

    private int id;
}

註解@SequenceGenerator上的allocationSize = N表示:每N個持久呼叫中一次從資料庫中獲取下一個值,在此之間將值區域性增加1。具體是什麼意思呢?通過對allocationSize屬性的顯式設定,此數字之後將再次進行資料庫查詢以獲取下一個資料庫序列值,預設情況下初始化值從1開始,且實體的主鍵始終將增加1,除非我們達到了該分配大小限制,一旦達到allocationSize後,將再次從資料庫序列中檢索下一個ID, 所以提高了效能。我們來舉一個例子來說明,如下:

@Entity
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "student_generator")
    @SequenceGenerator(name = "student_generator",sequenceName = "student_seq", allocationSize = 10)
    private int id;
}

我們將上述allocationSize設定為10,當進行第一個持久呼叫時,將從資料庫中獲取student_seq.next_val,隨後的持久呼叫將不會進入到資料庫,而是將在本地返回最後一個值+1,也就是說一直在記憶體中進行,直到該值達到限制10,這樣就可以節省9次資料庫讀取,若有兩個實體管理器試圖做同一件事怎麼辦?當第一個實體管理器呼叫student_seq.next_val時,它將獲得1,第二個實體將得到的主鍵值為11,因此,第一個實體管理器將繼續像1、2、3... 10,第二個實體管理器將繼續像11、 12、13 ... 20,然後提取下一個student_seq.next_val。此時通過控制檯所對序列表所生成的SQL語句如下:

insert into student_seq values ( 1 )

select next_val as id_val from student_seq for update

update student_seq set next_val= ? where next_val=?

select next_val as id_val from student_seq for update

update student_seq set next_val= ? where next_val=?

 

我們看到上述對序列表的更新只執行了兩次SQL操作,第一次則是插入1,然後更新為next_val = 11,第二次則是next_val = 21,具體如何計算想必不用再多講。如上述所講,通過設定此屬性的大小可減少與資料庫序列表的操作,從而提高效能,但是這將引來序列表Id和插入表的主鍵值Id不一致的問題,比如我們使用純JDBC,那麼獲取插入行的下一個ID將是個問題。若將該屬性設定為1,雖然解決了這個問題,但是,每次都會執行查詢,如果資料庫被其他應用程式訪問,那麼如果另一個應用程式同時使用相同的ID則會產生問題,如此看來,將該屬性設定為1並沒有什麼很大的問題。序列ID的生成始終都是下一次而分配,通過預設值將其保留為50,這很顯然太高了,如果我們將在一個會話中保留近50條記錄,這些記錄將不會持久儲存,並且將使用此特定會話和轉換來持久儲存,那麼它也將有所幫助,因此,在使用SequenceGenerator註解時,應始終使用allocationSize = 1, 對於大多數流行的關係資料庫而言,序列始終以1遞增為最佳。

 

到這裡為止我們詳細討論了序列號策略生成主鍵的各種配置。預設情況下,生成hibernate_sequence的序列表且該序列表中的next_val和對應表中的主鍵增長一致,若我們顯式配置generator屬性,此時將更改序列表名稱且此時序列表中的next_val將具有跳躍性,因為這種情況和通過添加註解@SequenceGenerator結果一致(預設allocationSize為50),若需要更改在記憶體中進行一次持久呼叫獲取下一次序列號Id時,則需要添加註解@SequenceGenerator並顯式配置allocationSize大小。那麼問題來了,Hibernate針對序列號的生成器又有哪幾種方式呢?在Hibernate 5之前針對序列號的生成器策略應該只有兩種(具體未考證):SequenceGenerator、SequenceHiLoGenerator,在Hibernate 5中這兩種生成器已被棄用,現在只有名為SequenceStyleGenerator一種生成器策略,在該生成器策略下有5種優化器:HILO、LEGACY_HILO、POOLED、POOED_LO、POOED_LOTL,具體請參看包【org.hibernate.id.enhanced】下的列舉StandardOptimizerDescriptor,截圖如下:

如下,當我們只是配置了主鍵的生成為序列號生成策略時,此時為上述列舉none,不會選擇任何優化器即此時序列號表的next_val和表主鍵自增長一致。

@Entity
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)

    private int id;
}

當我們針對上述註解@GeneratedValue,如下顯式配置generator屬性或通過註解@SequenceGenerator顯式配置allocationSizes時,此時將採用pooled【池化】優化器來解釋allocationSize,換句話說:從Hibernate 5開始,當JPA實體識別符號使用的分配大小大於1時,池優化器是Hibernate使用的預設基於序列的策略。

@Entity
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "student_seq")

}

或者

@Entity
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "student_generator")
    @SequenceGenerator(name = "student_generator",sequenceName = "student_seq",allocationSize = 3)
}

我們新增5條資料,此時在序列表中的next_val將為10,next_val值生成示意圖如下:

 

若我們需要修改基於池優化器的序列策略,比如將優化器修改成hilo(高低優化器),這個優化器主要是針對高低演算法的實現,我們可通過註解@GenericGenerator來指定,如下:

@Entity
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "student_seq")
    @GenericGenerator(
            name = "student_seq",
            strategy = "sequence",
            parameters = {
                    @Parameter(name = "sequence_name",  value = "student_seq"),
                    @Parameter(name = "initial_value",  value = "1"),
                    @Parameter(name = "increment_size",  value = "3"),
                    @Parameter(name = "optimizer", value = "hilo")
            }
    )

    private int id;
}

注意:在註解@GeneratedValue上通過屬性generator顯式指定序列表名稱時,儘量不要使用英文標點中的句號即【.】,因為Hibernate內建對此符號做了處理。

@Entity
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "student.seq")

    private int id;
}

如上將生成名為seq的序列表,若在上述基礎上繼續新增【.】,例如修改為student.seq1.seq2,此時將丟擲如下異常:

GenerationType.IDENTITY

該策略是最容易使用的,但從效能角度來看卻不是最佳的。它依靠自動遞增的資料庫列,並允許資料庫在每次插入操作時生成一個新值,從資料庫的角度來看非常有效,因為對自動增量列進行了高度優化,並且不需要任何其他語句。但是則此方法有一個很大的缺點,Hibernate需要每個管理實體的主鍵值,因此必須立即執行insert語句,這樣阻止了它使用其他優化技術(例如JDBC批處理)。

@Entity
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)

    private int id;
}

GenerationType.TABLE

針對該主鍵生成策略很少使用,所以就不多講了,它通過在資料庫表中儲存和更新其當前值來模擬序列,這需要使用悲觀鎖,該悲觀鎖將所有事務按順序排列,這會減慢應用程式的速度,因此,如果資料庫支援大多數流行的資料庫所支援的序列,則應首選基於序列號的主鍵生成策略。

@Entity
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE,generator = "student_generator")
    @TableGenerator(name="student_generator", table="id_generator")

    private int id;
}

總結

本節我們詳細介紹了Hibernate 5.x中關於主鍵生成的策略,當然我們若不指定註解@GenerateValue,那麼主鍵則需要顯式指定,針對IDNENTITY和SEQUENCE策略,即使我們顯式指定了主鍵,此時會被忽略而不會丟擲異常,我們重點介紹了基於序列的主鍵生成策略,同時我們也推薦使用基於序列的策略來自動生成主鍵。好了,本文到此結束,我們下節