上一節咱們講解了Hibernate命名策略,從本節咱們開始陸續講解屬性、關係等映射,本節咱們來說講主鍵的生成策略。算法
JPA規範支持4種不一樣的主鍵生成策略(AUTO、IDENTITY、SEQUENCE、TABLE),這些策略以編程方式生成主鍵值或使用數據庫功能(例如自動遞增或序列),咱們只需將@GeneratedValue註解添加到主鍵屬性上並選擇對應的生成策略。數據庫
它是默認的生成策略,並容許持久性提供程序選擇生成策略,若是使用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 (?, ?, ?, ?)
它是使用數據庫序列生成惟一值的方法,它須要其餘select語句才能從數據庫序列中獲取下一個值,但這對大多數應用程序沒有性能影響。若是應用程序必須保留大量的新實體,則可使用某些特定於Hibernate的優化來減小語句的數量。性能
@Entity public class Student { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private int id; }
經過上述咱們知道默認生成的序列表名稱爲hibernate_sequence,當咱們打開會話插入5條數據時,此時序列表中的next_val爲6,也就說序列表中的序列Id和表中主鍵自增的順序一致,以下:優化
針對主鍵經過序列號生成的策略還有一個註解@SequenceGenerator,咱們進行以下配置後,此時將生成名爲student_seq的序列表。spa
@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後的結果一致。hibernate
@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, 因此提升了性能。咱們來舉一個例子來講明,以下:3d
@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,所以,第一個實體管理器將繼續像一、二、3... 10,第二個實體管理器將繼續像十一、 十二、13 ... 20,而後提取下一個student_seq.next_val。此時經過控制檯所對序列表所生成的SQL語句以下:code
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,此時將拋出以下異常:
該策略是最容易使用的,但從性能角度來看卻不是最佳的。它依靠自動遞增的數據庫列,並容許數據庫在每次插入操做時生成一個新值,從數據庫的角度來看很是有效,由於對自動增量列進行了高度優化,而且不須要任何其餘語句。可是則此方法有一個很大的缺點,Hibernate須要每一個管理實體的主鍵值,所以必須當即執行insert語句,這樣阻止了它使用其餘優化技術(例如JDBC批處理)。
@Entity public class Student { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; }
針對該主鍵生成策略不多使用,因此就很少講了,它經過在數據庫表中存儲和更新其當前值來模擬序列,這須要使用悲觀鎖,該悲觀鎖將全部事務按順序排列,這會減慢應用程序的速度,所以,若是數據庫支持大多數流行的數據庫所支持的序列,則應首選基於序列號的主鍵生成策略。
@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策略,即便咱們顯式指定了主鍵,此時會被忽略而不會拋出異常,咱們重點介紹了基於序列的主鍵生成策略,同時咱們也推薦使用基於序列的策略來自動生成主鍵。好了,本文到此結束,咱們下節見。