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ụ:
Maven dependencies:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>5.2.12.Final</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>5.2.12.Final</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>6.0.6</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.18</version> <scope>provided</scope> </dependency> |
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:
1 2 3 4 5 |
CREATE TABLE `clazz` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(45) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
Entity của table này được định nghĩa ban đầ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.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; import lombok.Getter; import lombok.Setter; @Entity @Table @Getter @Setter public class Clazz { @Id @GeneratedValue private Long id; @Column private String name; } |
Nội dung tập tin cấu hình của JPA:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence_2_1.xsd"> <persistence-unit name="jpaexample" transaction-type="RESOURCE_LOCAL"> <class>com.huongdanjava.jpageneratedvalue.Clazz</class> <exclude-unlisted-classes>true</exclude-unlisted-classes> <properties> <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" /> <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/jpa_example" /> <property name="javax.persistence.jdbc.user" value="root" /> <property name="javax.persistence.jdbc.password" value="123456" /> <property name="hibernate.format_sql" value="true" /> <property name="hibernate.show_sql" value="true" /> <property name="hibernate.use_sql_comments" value="true" /> <property name="hibernate.id.new_generator_mappings" value="false" /> </properties> </persistence-unit> </persistence> |
Application class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
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(); Clazz clazz = new Clazz(); clazz.setName("Class A"); em.persist(clazz); transaction.commit(); } } |
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:
1 2 3 |
@Id @GeneratedValue(strategy = GenerationType.TABLE) private Long id; |
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:
1 |
<property name="hibernate.id.new_generator_mappings" value="true" /> |
thì cấu trúc của table hibernate_sequences sẽ như sau:
1 2 3 4 5 |
CREATE TABLE `hibernate_sequences` ( `sequence_name` VARCHAR(255) NOT NULL, `next_val` INT(19), PRIMARY KEY (`sequence_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
Còn nếu giá trị của property “hibernate.id.new_generator_mappings” là false:
1 |
<property name="hibernate.id.new_generator_mappings" value="false" /> |
thì cấu trúc của table hibernate_sequences sẽ như sau:
1 2 3 4 5 |
CREATE TABLE `hibernate_sequences` ( `sequence_name` VARCHAR(255) NOT NULL, `sequence_next_hi_value` INT(11), PRIMARY KEY (`sequence_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
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:
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:
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 = 1 ) @GeneratedValue(strategy = GenerationType.TABLE, generator = "clazz_gen") private Long id; |
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:
1 2 3 4 5 |
CREATE TABLE `id_gen` ( `gen_name` VARCHAR(255) NOT NULL, `gen_val` INT(11), PRIMARY KEY (`gen_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
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:
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à true, thì khi chạy với table mặc định của Hibernate:
1 2 3 |
@Id @GeneratedValue(strategy = GenerationType.TABLE) private Long id; |
các bạn sẽ thấy kết quả như sau:
Ở đâ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:
1 2 3 4 5 |
CREATE TABLE `student` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(45) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
Entity Student:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
package com.huongdanjava.jpageneratedvalue; import lombok.Getter; import lombok.Setter; import javax.persistence.*; @Entity @Table @Getter @Setter public class Student { @Id @GeneratedValue(strategy = GenerationType.TABLE) private Long id; @Column private String name; } |
rồi chạy ví dụ 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 |
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(); Clazz clazz = new Clazz(); clazz.setName("Class B"); em.persist(clazz); Student student = new Student(); student.setName("Khanh"); em.persist(student); transaction.commit(); } } |
thì các bạn sẽ thấy kết quả như sau:
Rõ 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:
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; |
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:
1 2 3 4 5 |
CREATE TABLE `id_gen` ( `gen_name` VARCHAR(255) NOT NULL, `gen_val` INT(11), PRIMARY KEY (`gen_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
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:
Bâ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.
Unnamed
Khánh cho hỏi allocationSize có nghĩa gì khi nào thì để mặc định, khi nào để allocationSize =1 thì tốt hơn
Khanh Nguyen
Bạn tham khảo thêm bài viết này nhé https://huongdanjava.com/vi/noi-them-ve-thuoc-tinh-allocationsize-trong-jpa.html
X981
Vậy cho mình hỏi thêm, giữa Hibernate độc lập và JPA thì nên dùng cái nào hơn ?
Khanh Nguyen
Đi làm đa số Khanh thấy dùng JPA, còn tuỳ trường hợp mà mình lựa chọn thôi bạn.
abc
cái file persistence.xml mình tự tạo hay eclipse sinh ra cho mình nhỉ anh
Khanh Nguyen
Mình tự tạo nha em.
X981
Cho mình hỏi giữa Hibernate trong bài viết trước và Hibernate trong bài viết này có gì khác nhau? Mình đang rối ở chỗ này.
Khanh Nguyen
Bài trước là bài nào bạn?
X981
“Hibernate và Spring framework” – 5 ngày trước.
Là bài này bạn.
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.