0

Apache Derby Bài 43: Sử dụng Derby In-Memory Mode cho Unit và Integration Test trong Spring Boot

1. Nỗi ám ảnh khi kiểm thử với cơ sở dữ liệu thật

Trong quy trình phát triển phần mềm chuẩn mực (như CI/CD), bạn phải viết các bài kiểm thử tự động (Unit Test / Integration Test) để đảm bảo mã nguồn hoạt động đúng.

Nếu bạn chạy các bài Test này trực tiếp trên máy chủ cơ sở dữ liệu thật (Network Server) hoặc CSDL lưu trên ổ cứng, bạn sẽ gặp phải 3 vấn đề lớn:

  1. Ô nhiễm dữ liệu (Data Pollution): Bài Test A chèn một nhân viên vào DB. Bài Test B đếm số lượng nhân viên và bị sai kết quả vì dính dữ liệu của bài Test A.

  2. Tốc độ rùa bò: Mỗi lần chạy bộ Test với hàng nghìn kịch bản, hệ thống phải thực hiện hàng nghìn thao tác đọc/ghi vật lý (I/O) xuống ổ cứng, làm quá trình CI/CD kéo dài hàng giờ.

  3. Phụ thuộc môi trường: Bạn không thể chạy Test nếu quên bật Server, hoặc cấu hình sai mật khẩu.

Để giải quyết bài toán này, Apache Derby cung cấp một "vũ khí bí mật": Chế độ In-Memory (Lưu trữ hoàn toàn trên RAM).

2. Bản chất của Derby In-Memory Mode

Ở chế độ In-Memory, Apache Derby không ghi bất kỳ dữ liệu nào xuống ổ cứng. Không có thư mục seg0, không có file log vật lý. Mọi cấu trúc bảng (Schema), dữ liệu (Rows), và chỉ mục (Indexes) đều nằm trọn trong bộ nhớ RAM của máy ảo Java (JVM).

  • Siêu tốc: Tốc độ truy vấn và thêm/sửa/xóa nhanh gấp hàng chục lần so với lưu trên đĩa.

  • Tự động dọn dẹp: Ngay khi bài Test kết thúc (JVM tắt), toàn bộ dữ liệu sẽ tự động bốc hơi sạch sẽ, trả lại trạng thái nguyên sơ cho lần chạy Test tiếp theo.

Cú pháp JDBC URL In-Memory: Thay vì chỉ định đường dẫn thư mục, bạn thêm từ khóa memory: vào giữa:

Plaintext

jdbc:derby:memory:ten_db_thu_nghiem;create=true

3. Cấu hình Spring Boot cho môi trường Test

Trong Spring Boot, chúng ta thường tách biệt cấu hình của môi trường chạy thật (application.properties) và môi trường kiểm thử.

Tạo một tệp cấu hình mới nằm trong thư mục src/test/resources/application-test.properties:

Properties

# 1. Trỏ JDBC URL vào RAM (In-Memory)
spring.datasource.url=jdbc:derby:memory:test_db;create=true
spring.datasource.driver-class-name=org.apache.derby.jdbc.EmbeddedDriver

# 2. Không cần User/Pass cho môi trường Test cục bộ (hoặc để trống)
spring.datasource.username=
spring.datasource.password=

# 3. Yêu cầu Hibernate tự động tạo bảng (DDL) từ Class Entity mỗi khi chạy Test
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.database-platform=org.hibernate.dialect.DerbyDialect
spring.jpa.show-sql=true

(Lưu ý: Để chạy được In-Memory, bạn cần đảm bảo thư viện lõi derby có mặt trong scope test của pom.xml, vì derbyclient chỉ dùng để gọi mạng).

XML

<dependency>
    <groupId>org.apache.derby</groupId>
    <artifactId>derby</artifactId>
    <scope>test</scope>
</dependency>

4. Viết Integration Test với @DataJpaTest

Spring Boot cung cấp annotation @DataJpaTest chuyên dụng để kiểm thử tầng CSDL. Nó sẽ chỉ nạp các Bean liên quan đến JPA (như Repository, Entity) giúp bài Test chạy rất nhanh.

Dưới đây là mã nguồn kiểm thử cho NhanVienRepository (từ Bài 42):

Java

package com.hieu.derby.repository;

import com.hieu.derby.entity.NhanVien;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.TestPropertySource;

import java.util.List;

@DataJpaTest
// Yêu cầu Spring Boot KHÔNG tự động thay thế DB của chúng ta bằng H2 mặc định,
// mà phải dùng cấu hình Derby In-Memory trong file properties.
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
// Chỉ định file cấu hình môi trường test
@TestPropertySource(locations = "classpath:application-test.properties")
public class NhanVienRepositoryTest {

    @Autowired
    private NhanVienRepository nhanVienRepository;

    // Chạy trước mỗi hàm @Test để chuẩn bị dữ liệu mẫu
    @BeforeEach
    public void setUp() {
        nhanVienRepository.save(new NhanVien("Alice", "Developer", 2000.0));
        nhanVienRepository.save(new NhanVien("Bob", "Manager", 3000.0));
        nhanVienRepository.save(new NhanVien("Charlie", "Developer", 2500.0));
    }

    @Test
    public void testTuyenDungNhanVienMoi_SinhIdTuDong() {
        // Dữ liệu được chèn hoàn toàn vào RAM, siêu nhanh!
        NhanVien nv = new NhanVien("David", "Tester", 1500.0);
        NhanVien savedNv = nhanVienRepository.save(nv);

        Assertions.assertNotNull(savedNv.getId(), "ID phải được tự động sinh ra");
        Assertions.assertEquals("David", savedNv.getHoTen());
    }

    @Test
    public void testTimKiemTheoChucVu() {
        List<NhanVien> devs = nhanVienRepository.findByChucVu("Developer");
        
        Assertions.assertEquals(2, devs.size(), "Phải tìm thấy đúng 2 Developer");
    }
}

5. Xóa dữ liệu In-Memory (Drop Database)

Mặc dù dữ liệu trên RAM sẽ mất khi tắt JVM, nhưng nếu bạn có một chuỗi hàng trăm Test Class chạy liên tiếp, dữ liệu In-Memory có thể bị tích tụ và rò rỉ.

Derby hỗ trợ lệnh xóa sạch DB khỏi bộ nhớ (Drop) ngay trong URL kết nối. Spring Boot xử lý việc này khá mượt với cấu hình create-drop, nhưng nếu bạn viết JDBC thuần trong Test, bạn có thể gọi URL sau ở hàm @AfterAll:

Plaintext

jdbc:derby:memory:test_db;drop=true

Khi gọi URL này, Derby sẽ ném ra một ngoại lệ SQLState = 08006 báo hiệu cơ sở dữ liệu đã bị xóa sổ hoàn toàn khỏi RAM. (Đó là một ngoại lệ chủ đích để xác nhận thao tác thành công).

6. Tổng kết

  • In-Memory Mode (jdbc:derby:memory:) là giải pháp hoàn hảo để thực hiện Integration Test cho các thao tác CSDL.

  • Khắc phục được rủi ro ô nhiễm dữ liệu và sự chậm chạp của I/O ổ đĩa.

  • Kết hợp với @DataJpaTest và @AutoConfigureTestDatabase(replace = Replace.NONE) của Spring Boot, bạn có một quy trình kiểm thử cực kỳ chuyên nghiệp và đáng tin cậy ngay trên máy cá nhân lẫn hệ thống CI (như Jenkins, GitHub Actions).

Đến đây, chúng ta đã nắm bắt toàn bộ chu trình phát triển với Apache Derby từ cấu hình, thiết kế, tối ưu cho tới kiểm thử. Bài tiếp theo sẽ là một mảnh ghép thực tế quan trọng: làm sao để gói gọn cả Ứng dụng Java và CSDL Derby thành một file chạy duy nhất phân phối cho khách hàng.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí