Chắc chắn trong công việc bạn đã từng làm việc với nhiều loại cơ sở dữ liệu khác nhau: Oracle, MySQL, MS SQL, DB2, … và chúng có những cách sinh khóa chính khác nhau: Hoặc là kiểu tự tăng hoặc sử dụng Sequence. Giả sử khóa chính chúng ta sử dụng thuộc kiểu số (numeric)

Nhưng khi viết mã thiết kế các lớp Entity, bạn cần phải xác định cụ thể chiến lược sinh khóa phù hợp cho các đối tượng Entity này khi chúng được tạo ra và được lưu trữ trong database. Tùy vào nhu cầu hệ thống mà bạn phát triển, khả năng mở rộng và tính khả chuyển.

Sau đây là 3 chiến lược để sinh khóa chính cho bảng dữ liệu bằng cách sử dụng chú thích hướng dẫn @GeneratedValue phù hợp.

1. Chiến lược GenerationType.IDENTITY

Các cơ sở dữ liệu MySQL hoặc Microsoft SQL Server cung cấp cơ chế sinh khóa tự động trong quá trình Insert dữ liệu. Có thể trước đây bạn đã từng nhìn thấy mã SQL thiết kế sinh khóa tự động như thế này đối với MySQL:

CREATE TABLE APP_USERS
(
    APP_USERS_PK BIGINT NOT NULL AUTO_INCREMENT,
    USERNAME VARCHAR(255) NOT NULL,
    PASSWORD VARCHAR(255) NOT NULL,
    PRIMARY KEY(APP_USERS_PK),
    UNIQUE(USERNAME)
);

Chúng ta thiết kế bảng lưu trữ người dùng với trường khóa chính APP_USERS_PK và được đánh dấu là AUTO_INCREAMENT, điều này cho phép database thêm vào số tiếp theo khi một bản ghi mới được insert vào.

Như vậy, tương ứng, khi làm việc với JPA, chúng ta cần thêm một chú thích hướng dẫn @GeneratedValue với chiến lược strategy = GenerationType.IDENTITY.

@Entity
@Table( name = "APP_USERS", catalog = "SampleDBName", schema = "" )
public class AppUsersEntity implements Serializable
{
    @Id
    @GeneratedValue( strategy = GenerationType.IDENTITY )
    @Column( name = "APP_USERS_PK" )
    private Long appUsersPk;
    
    //The rest of the codes...
}

Việc làm này cho phép Entity mang tính chất AUTO_INCREAMENT và có khả năng tự động sinh khóa chính tự động khi bản ghi dữ liệu được thêm vào cơ sở dữ liệu.

Các cơ sở dữ liệu tương thích với chiến lược này

  • MySQL
  • Microsoft SQL Server
  • IBM DB2 ver 7.1 and later

2. Chiến lược GenerationType.SEQUENCE

Các cơ sở dữ liệu như Oralce thì lại có thiết kế bộ sinh khóa chính khác hoàn toàn, nó dùng một đối tượng là các SEQUENCE để sinh khóa. Ở một khía cạnh nào đó, cơ chế này có tính linh hoạt hơn và cung cấp kiểm soát nhiều hơn cho các ứng dụng. Ví dụ về một tập lệnh tạo bảng trong Oracle:

CREATE TABLE APP_USERS
(
    APP_USERS_PK NUMBER(10) NOT NULL,
    USERNAME VARCHAR2(255) NOT NULL,
    PASSWORD VARCHAR2(255) NOT NULL
);

ALTER TABLE APP_USERS ADD CONSTRAINT APP_USERS_C1 PRIMARY KEY(APP_USERS_PK);
ALTER TABLE APP_USERS ADD CONSTRAINT APP_USERS_C2 UNIQUE(USERNAME);

CREATE SEQUENCE APP_USERS_SEQ START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE;

Nhìn vào đoạn mã SQL trên, một sequence APP_USERS_SEQ được tạo ra để sinh các giá trị số và nó sẽ được sử dụng cho trường khóa chính (về sau). Để gọi và sử dụng APP_USERS_SEQ cho một Entity khi được tạo ra trong database, tương ứng ta sẽ thêm vào một chú thích khóa chính báo cho JPA biết chúng ta đang sử dụng chiến lược sinh khóa sử dụng SEQUENCE như sau:

@Entity
@Table( name = "APP_USERS", catalog = "", schema = "SampleDatabaseSchema" )
public class AppUsersEntity implements Serializable
{
    @Id
    @SequenceGenerator( name = "appUsersSeq", sequenceName = "APP_USERS_SEQ", allocationSize = 1, initialValue = 1 )
    @GeneratedValue( strategy = GenerationType.SEQUENCE, generator = "appUsersSeq" )
    @Column( name = "APP_USERS_PK" )
    private Long appUsersPk;
    
    //The rest of the codes...
}

Trong đoạn mã trên thì thuộc tính name của @SequenceGenerator được đặt tên bởi bạn, nó có thể là bất cứ gì nhưng phải là duy nhất trong chương trình 😀 (tức là tên này không được phép đặt cho các trường @Id khác của các Entity khác. Thuộc tính sequenceName thì bạn PHẢI điền chính xác với tên sequence đã tạo ra bởi câu lệnh SQL CREATE SEQUENCE APP_USERS_SEQ ..., trong trường hợp của chúng ta là APP_USERS_SEQ. Sau đó là chú thích mã @GeneratedValue chỉ ra strategy là GenerationType.SEQUENCE và thuộc tính generator có giá trị là tên mà bạn đã định nghĩa và đặt tên cho SequenceGenerator (dòng 6) cụ thể nó là “appUsersSeq”.

Các cơ sở dữ liệu tương thích với chiến lược này

  • Oracle DB
  • PostgreSQL
  • IBM DB2 ver 7.2

3. Chiến lược GenerationType.TABLE

Nếu như bạn muốn xây dựng một ứng dụng Web doanh nghiệp (Enterprise Web Application) có tính khả chuyển, dễ dàng thích khi và có thể triển khai với nhiều loại cơ sở dữ liệu khác nhau, thì cách tốt nhất là thiết kế một bảng để lưu trữ các khóa chính được sinh ra (coi như đây là một bảng dữ liệu bình thường, độc lập và không phụ thuộc vào cơ sở dữ liệu nào cả). Cách tiếp cận này rất tự do, bạn có thể cho phép ứng dụng này hoạt động với bất cứ database nào nếu muốn. Xem ví dụ sau đây

Đối với MySQL:

CREATE TABLE APP_USERS
(
    APP_USERS_PK BIGINT NOT NULL,
    USERNAME VARCHAR(255) NOT NULL,
    PASSWORD VARCHAR(255) NOT NULL,
    PRIMARY KEY(APP_USERS_PK),
    UNIQUE(USERNAME)
);
 
CREATE TABLE APP_SEQ_STORE
(
 APP_SEQ_NAME VARCHAR(255) NOT NULL,
 APP_SEQ_VALUE BIGINT NOT NULL,
 PRIMARY KEY(APP_SEQ_NAME)
);
 
INSERT INTO APP_SEQ_STORE VALUES ('APP_USERS.APP_USERS.PK', 0);

Ở trên, chúng ta sử dụng cấu trúc bảng tương tự như ví dụ đầu, nhưng thêm vào một định nghĩa bảng lưu trữ các giá trị khóa chính cho các bảng. Bảng APP_SEQ_STORE là một bảng chỉ chứa cặp giá trị kiểu <name, value> và chúng ta cũng chèn sẵn 1 bản ghi APP_USERS.APP_USERS.PK với giá trị 0 sẽ đại diện cho bộ sinh khóa chính đối với mỗi single entity duy nhất.

(*) Chú ý: PHẢI khởi tạo các giá trị ban đầu cho bảng APP_SEQ_STORE cho mỗi entity tương ứng khi sử dụng chiến lược GenerationType.TABLE

Mã java thiết kế entity sử dụng chiến lược GenerationType.TABLE như sau:

@Entity
@Table( name = "APP_USERS", catalog = "SampleDBName", schema = "" )
public class AppUsersEntity implements Serializable
{
    @Id
    @Column( name = "APP_USERS_PK" )
    @TableGenerator( name = "appSeqStore", table = "APP_SEQ_STORE", pkColumnName = "APP_SEQ_NAME", pkColumnValue = "APP_USERS.APP_USERS_PK", valueColumnName = "APP_SEQ_VALUE", initialValue = 1, allocationSize = 1 )
    @GeneratedValue( strategy = GenerationType.TABLE, generator = "appSeqStore" )
    private Long appUsersPk;
    
    //The rest of the codes...
}

Đối với chiến lược này, bạn sẽ khai báo một chỉ dẫn @TableGenerator. Cung cấp tên duy nhất với thuộc tính name, và bạn có thể đặt tùy ý, ví dụ như: AppUsersEntity_PK chẳng hạn, nhưng ở trong ví dụ trên chúng ta đặt là appSeqStore. Cụ thể chi tiết như sau:

  1. name: tên định danh cho table generator, đặt tùy ý và duy nhất theo cách của bạn
  2. table: tên bảng lưu trữ các bộ sinh khóa
  3. pkColumnName: cột của bảng tương ứng với sequence name
  4. pkColumnValue: bản ghi tương ứng lưu trữ bộ sinh khóa chính
  5. valueColumnName: cột lưu giá trị hiện thời của bộ đếm, khi vượt qua ngưỡng này, giá trị tiếp theo sẽ được cộng(+) thêm với allocationSize
  6. initialValue: giá trị khởi đầu bằng 1
  7. allocationSize: bộ nhớ đệm để tăng các giá trị số tuần tự tiếp theo

Sau đó, giống với các chiến lược khác, chúng ta cũng khai báo một bộ sinh khóa @GeneratedValue, tất nhiên là GenerationType.TABLEgenerator=appSeqStore đã khai báo trước.

OK, như vậy là xong. Khi các entity được khởi tạo, chiến lược sinh khóa sẽ tự động xác định giá trị tuần tự tiếp theo là số bao nhiêu và gán làm @Id cho đối tượng này!

4. Lựa chọn chiến lược tốt nhất

Nếu bạn và động đội đang phát triển ứng dụng doanh nghiệp và xác định cụ thể cơ sở dữ liệu lưu trữ là gì và sử dụng nó trong suốt vòng đời của ứng dụng thì các chiến lược IDENTITYSEQUENCE là lựa chọn tốt nhất cho bạn, vì nó sẽ giảm tránh một số các thao tác phức tạp để thiết các lớp Entity và trong giai đoạn triển khai.

Tuy nhiên, nếu ứng dụng bạn phát triển là một sản phẩm mà có thể triển khai và dữ liệu có thể lưu trữ đối với bất kỳ database nào sử dụng. Thì TABLE là chiến lược lựa chọn tốt nhất !

Đánh đổi lại, chiến lược TABLE bạn cần cấu hình thêm một vài bước nữa và mất chút thời gian. Hi vọng trên đây giúp bạn lựa chọn được chiến lược thiết kế tốt nhất !

Liên kết tham khảo

About The Author