Tìm hiểu về annotation @GeneratedValue trong JPA – Phần 1

Khi định nghĩa một primary key của một table trong database, chúng ta thường thêm chữ AUTO_INCREMENT. Với định nghĩa này, chúng ta không cần quan tâm đến giá trị của cột primary key mỗi khi chúng ta thêm một dữ liệu mới vào bảng, giá trị này sẽ được gán tự động và là duy nhất. Trong JPA, để làm được điều này, chúng ta phải khai báo thêm cho thuộc tính primary key của entity một annotation là @GeneratedValue. Cụ thể như thế nào? Chúng ta hãy cùng nhau tìm hiểu về annotation @GeneratedValue trong JPA trong bài viết này nhé các bạn!

Đầu tiên, mình sẽ tạo một Maven project để làm ví dụ:

Tìm hiểu về annotation @GeneratedValue trong JPA

Maven dependencies:

Trong bài viết này, mình sẽ dùng database là MySQL với một table tên là clazz, được định nghĩa với cấu trúc như sau:

Entity của table này được định nghĩa ban đầu như sau:

Nội dung tập tin cấu hình của JPA:

Application class:

OK, vậy là mọi thứ chuẩn bị đã xong, giờ chúng ta sẽ đi vào chủ đề chính của bài viết này nhé các bạn!

Như mình đã nói, trong JPA, để những cột primary key của một table trong database tự động được gán giá trị mỗi khi chúng ta insert một dòng và giá trị này là duy nhất, chúng ta sẽ sử dụng annotation @GeneratedValue khi khai báo những cột primary key đó. Có 4 cách khác nhau để chúng ta làm được điều này, tuỳ thuộc vào giá trị của thuộc tính “strategy” mà chúng ta khai báo trong annotation @GeneratedValue.

Strategy đầu tiên mà mình muốn nói ở đây là strategy GenerationType.TABLE

Các bạn khai báo strategy này trong entity Clazz như sau:

Với strategy này, như tên gọi, chúng ta sẽ cần định nghĩa một table khác để lưu trữ thông tin cho việc generate giá trị cho cột primary key.

Mặc định, nếu các bạn sử dụng Hibernate là implementation của JPA thì tên của table này là hibernate_sequences. Hiện tại có 2 phiên bản định nghĩa cấu trúc của table này, tuỳ theo giá trị của thuộc tính “hibernate.id.new_generator_mappings”.

Nếu trong tập tin cấu hình của JPA, các bạn không khai báo property “hibernate.id.new_generator_mappings” hoặc có khai báo nhưng giá trị của nó là true:

thì cấu trúc của table hibernate_sequences sẽ như sau:

Còn nếu giá trị của property “hibernate.id.new_generator_mappings” là false:

thì cấu trúc của table hibernate_sequences sẽ như sau:




Trong ví dụ này, nếu mình set giá trị của property “hibernate.id.new_generator_mappings” là false thì khi chạy các bạn sẽ thấy kết quả như sau:

Tìm hiểu về annotation @GeneratedValue trong JPA

Như các bạn thấy, mình đã chạy 3 lần và với mỗi lần chạy, JPA sẽ lấy giá trị hiện tại của cột sequence_next_hi_value trong bảng hibernate_sequences, sau đó tăng giá trị này lên 1 đơn vị, rồi cập nhập cột sequence_next_hi_value với giá trị mới cho sequence_name là Clazz (Clazz chính là tên entity của chúng ta đấy các bạn) và cuối cùng là insert record mới cho bảng clazz. Ở lần chạy đầu tiên, vì không có record nào trong bảng hibernate_sequences nên lúc đó giá trị của cột sequence_next_hi_value sẽ là 1. Và việc lấy giá trị hiện tại của cột sequence_next_hi_value là để xác định giá trị cần generate cho cột primary key trong bảng clazz.

Giá trị của cột id trong bảng clazz, ở lần đầu tiên khi chưa có record nào trong bảng clazz thì giá trị sẽ là 1 nhưng sau đó, cứ mỗi lần chạy lại tăng lên 32768. Giá trị của cột này ở lần insert thứ n sẽ bằng giá trị của cột sequence_next_hi_value trong bảng hibernate_sequences ở lần chạy đó nhân với 32768. Con số 32768 này gọi là allocationSize mặc định của Hibernate đấy các bạn. Thật ra nó là kết quả của một thuật toán, gọi là thuật toán hi/lo. Các bạn có thể tìm hiểu thêm về thuật toán hi/lo này trên internet nếu muốn.

Trong trường hợp các bạn không muốn sử dụng table hoặc allocationSize mặc định của Hibernate, các bạn có thể định nghĩa để sử dụng một table khác với annotation @TableGenerator. Cụ thể, chúng ta có thể khai báo như sau:

Trong khai báo trên, mình đã định nghĩa để sử dụng một table tên là id_gen cùng với 2 cột là gen_name, gen_val:

và allocationSize là 1.

Phần quan trọng nhất là các bạn cần phải khai báo thêm thuộc tính generator cho annotation @GeneratedValue với giá trị là giá trị của thuộc tính name trong annotation @TableGenerator.

Khi đó, chạy lại ví dụ với database được tạo mới lại, các bạn sẽ thấy kết quả như sau:

Tìm hiểu về annotation @GeneratedValue trong JPA

Như các bạn thấy giá trị của cột id trong bảng clazz bây giờ tăng từng đơn vị chứ không giống như khi chúng ta sử dụng table mặc định của Hibernate nữa.



Trong trường hợp giá trị của property “hibernate.id.new_generator_mappings” là truethì khi chạy với table mặc định của Hibernate:

các bạn sẽ thấy kết quả như sau:

Tìm hiểu về annotation @GeneratedValue trong JPA

Ở đây, mình cũng đã chạy 3 lần và không giống như trường hợp giá trị của property “hibernate.id.new_generator_mappings” bằng false, ở trường hợp này, chỉ có một giá trị default cho cột sequence_name trong bảng hibernate_sequences. Tất cả các entity mỗi khi được insert vào các table của database sẽ truy vấn đến giá trị của cột next_val của cột sequence_name với giá trị default trong bảng hibernate_sequences để lấy giá trị cho cột primary key.

Giá trị của cột next_val mỗi lần chạy sẽ tăng lên một đơn vị. Đây cũng là con số allocationSize mặc định trong trường hợp này. Ở lần đầu tiên, khi chưa có record nào trong bảng hibernate_sequences, Một record mới sẽ được thêm vào với next_val là 0, sau đó sẽ tăng lên 1.

Ví dụ như giờ mình thêm một table nữa, tên là student, với cấu trúc như sau:

Entity Student:

rồi chạy ví dụ sau:

thì các bạn sẽ thấy kết quả như sau:

Tìm hiểu về annotation @GeneratedValue trong JPARõ ràng là với trường hợp này, tất cả các entity đều sử dụng chung 1 giá trị default cho sequence_name để generate giá trị cho cột primary key của mình.

Giống như trường hợp giá trị của propery “hibernate.id.new_generator_mappings” bằng false, chúng ta cũng có thể định nghĩa 1 table khác để sử dụng thay vì sử dụng table mặc định của Hibernate như sau:

Trong khai báo trên, mình cũng đã định nghĩa để sử dụng một table tên là id_gen cùng với 2 cột là gen_name, gen_val:

và allocationSize là 2.

Phần quan trọng nhất là các bạn cần phải khai báo thêm thuộc tính generator cho annotation @GeneratedValue với giá trị là giá trị của thuộc tính name trong annotation @TableGenerator.

Khi đó, chạy lại ví dụ với database được tạo mới lại, các bạn sẽ thấy kết quả như sau:

Tìm hiểu về annotation @GeneratedValue trong JPABây giờ thì gen_name không phải là giá trị mặc định nữa, mà nó sẽ là tên của entity.

Lần đầu tiên chạy, khi chưa có bất kỳ record nào trong bảng id_gen, giá trị của gen_val sẽ là 5, và giá trị của cột primary key trong bảng clazz sẽ là 1. Những lần chạy và insert record tiếp theo, giá trị của cột gen_val sẽ được cập nhập bằng giá trị hiện tại của cột này cộng với allocationSize. Còn giá trị của cột primary key trong bảng clazz sẽ bằng giá trị hiện tại trước khi được cập nhập của cột gen_val trong bảng id_gen trừ đi 1.



Chia sẽ bài viết này ...Share on Facebook
Facebook
0Tweet about this on Twitter
Twitter
Share on LinkedIn
Linkedin

10 thoughts on “Tìm hiểu về annotation @GeneratedValue trong JPA – Phần 1

        1. Khanh Nguyen

          Khác nhau nha bạn!
          Hibernate có 2 phiên bản: một phiên bản là implementation cho JPA và một phiên bản khác của riêng nó với SessionFactory interface.
          Hibernate mình đề cập trong bài viết này là phiên bản implementation cho JPA.

Add Comment