Hướng dẫn nạp sẵn dữ liệu khi khởi động Spring Boot

18 tháng 05, 2022 - 374 lượt xem

Written by : Nguyen Quoc Thai
Gmail: thaithedoublecheese1@gmail.com

1. Tổng quát

Spring Boot giúp bạn thực sự dễ dàng quản lý các thay đổi trong cơ sở dữ liệu của mình. Nếu chúng ta để cấu hình mặc định, nó sẽ tìm kiếm các thực thể trong các gói của chúng ta và tự động tạo các bảng tương ứng.

Spring_DB

2. File dữ liệu data.sql

Chúng ta hãy cùng đặt giả thiết ở đây rằng chúng ta đang làm việc với JPA và xác định một thực thể Country đơn giản trong dự án của chúng ta:

@Entity
public class Country {

 @Id
 @GeneratedValue(strategy = IDENTITY)
 private Integer id;
 
 @Column(nullable = false)
 private String name;

 //...
}

Nếu chúng ta chạy ứng dụng của mình, Spring Boot sẽ tạo một bảng trống cho chúng ta nhưng sẽ không điền vào nó bất cứ thứ gì.

Một cách dễ dàng để làm điều này là tạo một file có tên data.sql:

INSERT INTO country (name) VALUES ('India');
INSERT INTO country (name) VALUES ('Brazil');
INSERT INTO country (name) VALUES ('USA');
INSERT INTO country (name) VALUES ('Italy');

Khi chúng ta chạy dự án với file này trên classpath, Spring sẽ lấy và sử dụng nó để điền cơ sở dữ liệu cho bảng.

3. File scheme.sql

Đôi khi, chúng ta không muốn dựa vào cơ chế tạo lược đồ mặc định.
Trong những trường hợp như vậy, chúng ta có thể tạo file schema.sql tùy chỉnh:

CREATE TABLE country (
    id   INTEGER      NOT NULL AUTO_INCREMENT,
    name VARCHAR(128) NOT NULL,
    PRIMARY KEY (id)
);

Spring sẽ lấy file này và sử dụng nó để tạo một lược đồ.

Tuy nhiên hãy lưu ý rằng khởi tạo dựa trên tập lệnh tức là thông qua schema.sql và data.sql và khởi tạo Hibernate cùng nhau có thể gây ra một số vấn đề.

Hoặc chúng ta tắt tính năng Hibernate tạo lược đồ tự động :

spring.jpa.hibernate.ddl-auto=none

Điều này sẽ đảm bảo rằng quá trình khởi tạo dựa trên tập lệnh được thực hiện trực tiếp bằng cách sử dụng schema.sql và data.sql.
Nếu chúng ta vẫn muốn có cả Hibernate tạo lược đồ tự động kết hợp với tạo lược đồ dựa trên tập lệnh và tập hợp dữ liệu, chúng ta sẽ phải sử dụng:

spring.jpa.defer-datasource-initialization=true

Điều này sẽ đảm bảo rằng sau khi Hibernate tạo lược đồ được thực hiện thì sau đó schema.sql sẽ được đọc cho bất kỳ lược đồ thay đổi bổ sung nào và file data.sql được thực thi để điền vào cơ sở dữ liệu.

Ngoài ra, khởi tạo dựa trên tập lệnh chỉ được thực hiện mặc định đối với cơ sở dữ liệu nhúng, để luôn khởi tạo cơ sở dữ liệu bằng tập lệnh, chúng ta sẽ phải sử dụng:

spring.sql.init.mode=always

Vui lòng tham khảo thêm tài liệu chính thức của Spring về cách khởi tạo cơ sở dữ liệu bằng tập lệnh SQL.

4. Cấu hình cơ sở dữ liệu bằng cách sử dụng Hibernate

Spring cung cấp một thuộc tính JPA cụ thể mà Hibernate sử dụng để tạo DDL: spring.jpa.hibernate.ddl-auto.
Các giá trị tiêu chuẩn của thuộc tính Hibernate là tạo, cập nhật, tạo-xóa, xác thực và none:

  • create (tạo) : Trước tiên Hibernate bỏ các bảng hiện có và sau đó tạo các bảng mới.
  • update (cập nhật): Mô hình đối tượng được tạo dựa trên ánh xạ (Annotations hoặc XML) được so sánh với lược đồ hiện có, sau đó Hibernate cập nhật lược đồ theo sự khác biệt. Nó không bao giờ xóa các bảng hoặc cột hiện có ngay cả khi chúng không còn được ứng dụng yêu cầu.
  • create - drop (tạo - xóa): Tương tự như tạo, với việc bổ sung Hibernate sẽ xóa cơ sở dữ liệu sau khi tất cả các hoạt động hoàn thành; thường được sử dụng cho unit testing.
  • validate (xác thực): Hibernate chỉ xác nhận xem các bảng và cột có tồn tại hay không. Nếu không, nó sẽ ném ra một ngoại lệ.
  • none: Giá trị này sẽ ngay lập tức tắt tạo DDL.

Trong Spring Boot sẽ mặc định giá trị tham số này là create-drop nếu không có trình quản lý lược đồ nào được phát hiện, nếu không thì none đối với tất cả các trường hợp khác.

Chúng ta phải đặt giá trị một cách cẩn thận hoặc sử dụng một trong các cơ chế khác để khởi tạo cơ sở dữ liệu.

5. Tùy chỉnh tạo lược đồ cơ sở dữ liệu

Theo mặc định, Spring Boot tự động tạo lược đồ của một Nguồn dữ liệu được nhúng.
Nếu chúng ta cần kiểm soát hoặc tùy chỉnh hành động này, chúng ta có thể sử dụng thuộc tính spring.sql.init.mode. Thuộc tính này nhận một trong ba giá trị:

  • always: luôn khởi tạo cơ sở dữ liệu
  • embedded: luôn khởi khởi tạo nếu một cơ sở dữ liệu nhúng đang được sử dụng. Đây là mặc định nếu giá trị thuộc tính không được chỉ định.
  • never: không bao giờ khởi tạo cơ sở dữ liệu

Đáng chú ý, nếu chúng ta đang sử dụng cơ sở dữ liệu không nhúng, giả sử MySQL hoặc PostGreSQL, muốn khởi tạo lược đồ của nó, chúng ta sẽ phải đặt thuộc tính này thành always.
Thuộc tính này đã được giới thiệu trong Spring Boot 2.5.0; chúng ta cần sử dụng spring.datasource.initialization-mode nếu chúng ta đang sử dụng các phiên bản trước của Spring Boot.

6. @SQL

Spring cũng cung cấp Annotatios @Sql - một cách khai báo để khởi tạo và điền vào lược đồ thử nghiệm của chúng ta.
Hãy xem cách sử dụng chú thích @Sql để tạo bảng mới và cũng tải bảng với dữ liệu ban đầu cho thử nghiệm tích hợp của chúng ta:

@Sql({"/employees_schema.sql", "/import_employees.sql"})
public class SpringBootInitialLoadIntegrationTest {

    @Autowired
    private EmployeeRepository employeeRepository;

    @Test
    public void testLoadDataForTestClass() {
        assertEquals(3, employeeRepository.findAll().size());
    }
}

Dưới đây là các thuộc tính của annotation @Sql:

  • config : cấu hình cục bộ cho các tập lệnh SQL. Chúng tôi sẽ mô tả chi tiết điều này trong phần tiếp theo.
  • executionPhase: Chúng ta cũng có thể chỉ định thời điểm thực thi các tập lệnh, BEFORE_TEST_METHOD hoặc AFTER_TEST_METHOD.
  • statements: Chúng ta có thể khai báo các câu lệnh inline SQL để thực thi.
  • scripts: Chúng ta có thể khai báo các đường dẫn đến các SQL script files để thực thi. Đây là alias cho giá trị thuộc tính.

annotation @Sql có thể được sử dụng ở cấp Class hoặc cấp Method.
Chúng ta sẽ tải thêm dữ liệu cần thiết cho một test case cụ thể bằng cách chú thích cho method đó:

@Test
@Sql({"/import_senior_employees.sql"})
public void testLoadDataForTestCase() {
    assertEquals(5, employeeRepository.findAll().size());
}

7. @SqlConfig

Chúng ta có thể cấu hình cách chúng ta phân tích cú pháp và chạy các tập lệnh SQL bằng cách sử dụng annotation @SqlConfig.
@SqlConfig có thể được khai báo ở cấp độ Class, nơi nó đóng vai trò như một cấu hình toàn cục. Hoặc chúng ta có thể sử dụng nó để cấu hình một annotation @Sql cụ thể.
Hãy cùng xem một ví dụ trong đó chúng ta chỉ định mã hóa các tập lệnh SQL của chúng ta cũng như chế độ transaction để thực thi các tập lệnh

@Test
@Sql(scripts = {"/import_senior_employees.sql"}, 
  config = @SqlConfig(encoding = "utf-8", transactionMode = TransactionMode.ISOLATED))
public void testLoadDataForTestCase() {
    assertEquals(5, employeeRepository.findAll().size());
}

hãy cùng xem các thuộc tính khác nhau của @SqlConfig:

  • blockCommentStartDelimiter: dấu phân cách để xác định điểm bắt đầu của khối chú thích trong các files SQL script
  • blockCommentEndDelimiter: dấu phân cách để biểu thị phần cuối của khối nhận xét trong các files SQL script
  • commentPrefix: là tiền tố để xác định các chú thích một dòng trong các files SQL script
  • dataSource: tên của bean javax.sql.DataSource mà các tập lệnh và câu lệnh sẽ được chạy
  • encoding: mã hóa cho các files SQL script, mặc định là nền tảng mã hóa
  • errorMode: chế độ sẽ được sử dụng khi gặp lỗi khi chạy các tập lệnh
  • separator: chuỗi dùng để phân tách các câu lệnh riêng lẻ; mặc định là “-“
  • transactionManager: tên bean của PlatformTransactionManager sẽ được sử dụng cho các transaction
  • transactionMode: chế độ sẽ được sử dụng khi thực thi các tập lệnh trong transction

8. @SqlGroup

Từ Java 8 trở lên cho phép sử dụng các annotations lặp lại. Chúng ta cũng có thể sử dụng tính năng này cho các anntation @Sql. Đối với Java 7 trở xuống, có container anntation - @SqlGroup.

Sử dụng annotation @SqlGroup, chúng ta sẽ khai báo nhiều annotation @Sql:

@SqlGroup({
  @Sql(scripts = "/employees_schema.sql", 
    config = @SqlConfig(transactionMode = TransactionMode.ISOLATED)),
  @Sql("/import_employees.sql")})
public class SpringBootSqlGroupAnnotationIntegrationTest {

    @Autowired
    private EmployeeRepository employeeRepository;

    @Test
    public void testLoadDataForTestCase() {
        assertEquals(3, employeeRepository.findAll().size());
    }
}

9. Kết luận

Trong bài viết nhanh này, chúng ta đã thấy cách có thể tận dụng các files schema.sql và data.sql để thiết lập một lược đồ ban đầu và điền dữ liệu vào nó. Chúng ta cũng đã biết cách sử dụng annotation @Sql, @SqlConfig@SqlGroup để tải dữ liệu test cho các tests.
Hãy nhớ rằng cách tiếp cận này phù hợp hơn với các tình huống cơ bản và đơn giản, và bất kỳ xử lý cơ sở dữ liệu nâng cao nào sẽ yêu cầu công cụ nâng cao và tinh chỉnh hơn như Liquibase hoặc Flyway.

Bình luận

avatar
Phạm Thị Mẫn 2022-05-18 10:02:49.125587 +0000 UTC

Tuyệt zời!

Avatar
* Vui lòng trước khi bình luận.
Ảnh đại diện
  +1 Thích
+1