Trong các bài viết trước phần 1 và phần 2 về annotation @GeneratedValue trong JPA, mình có đề cập về thuộc tính allocationSize khi sử dụng các strategy GenerationType.TABLE và GenerationType.SEQUENCE. Nhận thấy chúng không đủ chi tiết để các bạn hiểu về thuộc tính allocationSize, nên mình viết thêm bài viết này để discuss thêm về thuộc tính này, các bạn nhé!
Mình vẫn sẽ lấy ví dụ trong các bài viết trước về annotation @GeneratedValue để làm ví dụ cho bài viết này.
Để các bạn thấy rõ tác dụng của thuộc tính allocationSize, mình sẽ sửa lại code của class Application một xíu như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
package com.huongdanjava.jpageneratedvalue; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.Persistence; public class Application { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpaexample"); EntityManager em = emf.createEntityManager(); EntityTransaction transaction = em.getTransaction(); transaction.begin(); for (int i = 0 ; i < 5; i++) { Clazz clazz = new Clazz(); clazz.setName("Class B"); em.persist(clazz); } transaction.commit(); } } |
Như các bạn thấy, so với code trước đó, thay vì chỉ insert 1 record trong một transaction, mình insert 5 records một lần luôn.
Bây giờ, chúng ta sẽ discuss về thuộc tính allocationSize với strategy GenerationType.TABLE trước.
1 2 3 |
@Id @GeneratedValue(strategy = GenerationType.TABLE) private Long id; |
Như các bạn đã biết, chúng ta có 2 version để định nghĩa cấu trúc của table chứa thông tin sẽ được sử dụng để generate giá trị cho cột primary key, tuỳ thuộc vào giá trị của property “hibernate.id.new_generator_mappings”.
Trong trường hợp giá trị của property này là false, nếu chạy lại ví dụ trên, các bạn sẽ thấy kết quả như sau:
Như các bạn thấy, giá trị của cột id trong bảng clazz được generate theo một cách khác không giống như cách mà nó đã generate trong bài viết này.
Thay vì mỗi lần insert 1 record vào bảng clazz, giá trị của cột id của bảng này lại tăng lên 1 con số gọi là allocationSize mặc định thì giờ trong 1 lần chạy với 5 lần insert record, giá trị của cột id lại tăng lên 1 đơn vị.
Nguyên nhân là gì?
Các bạn đã biết cột sequence_next_hi_value trong bảng hibernate_sequences là cột sẽ lưu giữ thông tin để generate giá trị cho cột id trong bảng clazz. Sẽ như thế nào nếu cứ mỗi lần insert một record mới cho bảng clazz, các bạn lại phải truy vấn đến bảng hibernate_sequence để lấy giá trị cần phải generate cho cột id trong bảng clazz? Performance sẽ rất thấp đúng không các bạn?
Để giải quyết vấn đề này, JPA sử dụng khái niệm allocationSize để không cần phải truy vấn nhiều đến bảng hibernate_sequences nữa.
Trong 1 lần chạy ứng dụng, JPA sẽ nắm giữ tất cả các thông tin liên quan đến database.
Ở lần chạy đầu tiên và insert record đầu tiên vào bảng clazz như ví dụ trên, bảng hibernate_sequences sẽ không có giá trị cho entity Clazz, giá trị của cột id trong bảng clazz sẽ bắt đầu với 1. Ở lần insert thứ 2 vào bảng clazz của lần chạy này, JPA sẽ không truy vấn đến bảng hibernate_sequences để lấy giá trị cần generate cho cột id nữa. Vì hiện tại giá trị allocationSize mặc định của Hibernate lên tới 32768, nên JPA chỉ cần tăng giá trị của cột id trong bảng clazz lên 1 là được. Cứ thế cho đến khi nào giá trị của cột id trong bảng clazz chạm ngưỡng allocationSize thì lúc đó JPA mới truy vấn lại bảng hibernate_sequence để lấy mới giá trị cần generate cho cột id. allocationSize sẽ không thay đổi giá trị nên việc generate giá trị cho cột id trong bảng clazz cứ lặp đi lặp lại như thế cho đến khi các bạn tắt ứng dụng.
Nếu để ý câu lệnh SQL đến bảng hibernate_sequences, các bạn sẽ thấy kết quả cho lần chạy này như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
Hibernate: select sequence_next_hi_value from hibernate_sequences where sequence_name = 'Clazz' for update Hibernate: insert into hibernate_sequences (sequence_name, sequence_next_hi_value) values ('Clazz', ?) Hibernate: update hibernate_sequences set sequence_next_hi_value = ? where sequence_next_hi_value = ? and sequence_name = 'Clazz' |
Rõ ràng, JPA chỉ truy vấn đến bảng hibernate_sequences 1 lần và vì không thấy record nào cho entity Clazz nên nó insert mới và update giá trị cho bảng này.
Ở lần chạy thứ 2, JPA không nắm giữ thông tin của lần chạy trước đó, nên nó vẫn cứ truy vấn đến bảng hibernate_sequences để lấy giá trị cần generate cho cột id trong bảng clazz ở lần đầu tiên insert record vào bảng này. Sau đó thì cứ tăng giá trị của cột id mỗi lần insert mới record, lên từng đơn vị cho đến khi chạm ngưỡng allocationSize mà thôi.
Hãy xem câu lệnh SQL cho bảng hibernate_sequences ra sao nhé các bạn:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
Hibernate: select sequence_next_hi_value from hibernate_sequences where sequence_name = 'Clazz' for update Hibernate: update hibernate_sequences set sequence_next_hi_value = ? where sequence_next_hi_value = ? and sequence_name = 'Clazz' |
Các bạn sẽ thấy rõ tác dụng của thuộc tính allocationSize khi sử dụng customize table với giá trị allocationSize nhỏ hơn giá trị mặc định của Hibernate.
Ví dụ giờ mình định nghĩa customize table cho entity Clazz như sau:
1 2 3 4 5 6 7 8 9 10 |
@Id @TableGenerator( name = "clazz_gen", table = "id_gen", pkColumnName = "gen_name", valueColumnName = "gen_val", allocationSize = 2 ) @GeneratedValue(strategy = GenerationType.TABLE, generator = "clazz_gen") private Long id; |
Khi đó số lần truy vấn đến bảng id_gen sẽ nhiều hơn ở lần chạy đầu tiên như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
Hibernate: select gen_val from id_gen where gen_name = 'Clazz' for update Hibernate: insert into id_gen (gen_name, gen_val) values ('Clazz', ?) Hibernate: update id_gen set gen_val = ? where gen_val = ? and gen_name = 'Clazz' Hibernate: select gen_val from id_gen where gen_name = 'Clazz' for update Hibernate: update id_gen set gen_val = ? where gen_val = ? and gen_name = 'Clazz' Hibernate: select gen_val from id_gen where gen_name = 'Clazz' for update Hibernate: update id_gen set gen_val = ? where gen_val = ? and gen_name = 'Clazz' |
Các bạn lưu ý là nãy giờ chúng ta chỉ discuss cho trường hợp giá trị của property “hibernate.id.new_generator_mappings” là false thôi nhé các bạn!
Trong trường hợp giá trị của property “hibernate.id.new_generator_mappings” là true, chạy lại ví dụ, các bạn sẽ thấy kết quả như sau:
Bởi vì allocationSize mặc định trong trường hợp này của Hibernate chỉ là 1 nên các bạn sẽ thấy số lần truy vấn đến table mặc định của Hibernate (hibernate_sequences) nhiều (5 lần) như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
Hibernate: select tbl.next_val from hibernate_sequences tbl where tbl.sequence_name=? for update Hibernate: insert into hibernate_sequences (sequence_name, next_val) values (?,?) Hibernate: update hibernate_sequences set next_val=? where next_val=? and sequence_name=? Hibernate: select tbl.next_val from hibernate_sequences tbl where tbl.sequence_name=? for update Hibernate: update hibernate_sequences set next_val=? where next_val=? and sequence_name=? Hibernate: select tbl.next_val from hibernate_sequences tbl where tbl.sequence_name=? for update Hibernate: update hibernate_sequences set next_val=? where next_val=? and sequence_name=? Hibernate: select tbl.next_val from hibernate_sequences tbl where tbl.sequence_name=? for update Hibernate: update hibernate_sequences set next_val=? where next_val=? and sequence_name=? Hibernate: select tbl.next_val from hibernate_sequences tbl where tbl.sequence_name=? for update Hibernate: update hibernate_sequences set next_val=? where next_val=? and sequence_name=? |
Giờ chúng ta thử chạy với customize table xem sao nhé các bạn:
1 2 3 4 5 6 7 8 9 10 |
@Id @TableGenerator( name = "clazz_gen", table = "id_gen", pkColumnName = "gen_name", valueColumnName = "gen_val", allocationSize = 3 ) @GeneratedValue(strategy = GenerationType.TABLE, generator = "clazz_gen") private Long id; |
Kết quả:
Số lần truy vấn đến bảng id_gen sẽ thấp hơn nhiều (3 lần) so với giá trị allocationSize mặc định:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
Hibernate: select tbl.gen_val from id_gen tbl where tbl.gen_name=? for update Hibernate: insert into id_gen (gen_name, gen_val) values (?,?) Hibernate: update id_gen set gen_val=? where gen_val=? and gen_name=? Hibernate: select tbl.gen_val from id_gen tbl where tbl.gen_name=? for update Hibernate: update id_gen set gen_val=? where gen_val=? and gen_name=? Hibernate: select tbl.gen_val from id_gen tbl where tbl.gen_name=? for update Hibernate: update id_gen set gen_val=? where gen_val=? and gen_name=? |
Giờ thử xem với strategy GenerationType.SEQUENCE thì có gì khác biệt không nhé các bạn!
1 2 3 |
@Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private Long id; |
Ở trường hợp này, chúng ta chỉ xem xét với giá trị của property “hibernate.id.new_generator_mappings” là true thôi nhé các bạn.
Chạy lại ví dụ, các bạn sẽ thấy kết quả như sau:
Và số lần truy vấn đến bảng hibernate_sequence sẽ là 5 lần như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
Hibernate: select next_val as id_val from hibernate_sequence for update Hibernate: update hibernate_sequence set next_val= ? where next_val=? Hibernate: select next_val as id_val from hibernate_sequence for update Hibernate: update hibernate_sequence set next_val= ? where next_val=? Hibernate: select next_val as id_val from hibernate_sequence for update Hibernate: update hibernate_sequence set next_val= ? where next_val=? Hibernate: select next_val as id_val from hibernate_sequence for update Hibernate: update hibernate_sequence set next_val= ? where next_val=? Hibernate: select next_val as id_val from hibernate_sequence for update Hibernate: update hibernate_sequence set next_val= ? where next_val=? |
Còn với customize table thì sao nhỉ?
1 2 3 4 |
@Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequence_gen") @SequenceGenerator(name = "sequence_gen", sequenceName = "sequence", allocationSize = 3) private Long id; |
Kết quả như sau:
Và số lần truy vấn đến table sequence chỉ là 3 lần như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
Hibernate: select next_val as id_val from sequence for update Hibernate: update sequence set next_val= ? where next_val=? Hibernate: select next_val as id_val from sequence for update Hibernate: update sequence set next_val= ? where next_val=? Hibernate: select next_val as id_val from sequence for update Hibernate: update sequence set next_val= ? where next_val=? |
TÓM LẠI, như chúng ta đã discuss, trong trường hợp các bạn sử dụng strategy GenerationType.TABLE và GenerationType.SEQUENCE để generate giá trị cho các cột primary key trong các bảng trong database, việc sử dụng thuộc tính allocationSize cần được chú trọng để tăng performance cho ứng dụng của chúng ta. Giá trị của thuộc tính này càng cao thì performance càng tăng, nhưng việc cao cỡ nào thì cũng cần được xem xét nhé các bạn!