Trong bối cảnh hiện nay, nhiều dự án đang dịch chuyển sang sử dụng các hệ quản trị cơ sở dữ liệu nội địa. Trong số đó, Dameng Database (DM8) là một lựa chọn phổ biến mà nhiều nhà phát triển lựa chọn. Bài viết này sẽ hướng dẫn chi tiết cách triển khai phiên bản Dameng DM8 độc lập bằng Docker và cách thực hiện các thao tác CRUD (Tạo, Đọc, Cập nhật, Xóa) từ một ứng dụng Spring Boot.
I. Triển Khai Nhanh Cơ Sở Dữ Liệu Dameng với Docker
Để bắt đầu, chúng ta sẽ sử dụng Docker để triển khai DM8. Tài liệu chính thức về triển khai Dameng bằng Docker có sẵn trên trang web của Dameng, nơi bạn cũng có thể tìm thấy liên kết tải xuống các image Docker mới nhất.
Giả sử bạn đã tải xuống image Docker của Dameng, ví dụ: dm8_20241022_x86_rh6_64_single.tar, bạn cần tải file này lên máy chủ ảo (ví dụ: CentOS 7 với Docker đã cài đặt) và thực hiện lệnh sau để nạp image vào Docker:
docker load -i dm8_20241022_x86_rh6_64_single.tar
Sau khi nạp xong, bạn có thể kiểm tra danh sách image Docker bằng lệnh docker images để xác nhận.
Tiếp theo, hãy tạo một thư mục để lưu trữ dữ liệu của Dameng trên máy chủ ảo của bạn. Ví dụ, tạo thư mục /data/dm8/data:
mkdir -p /data/dm8/data
Trong thư mục /data/dm8/, tạo một file docker-compose.yml với nội dung sau để cấu hình và khởi chạy vùng chứa Dameng:
version: "3.2"
services:
dm8:
container_name: dm8_server
image: dm8_single:dm8_20241022_rev244896_x86_rh6_64 # Thay đổi theo phiên bản image bạn đã tải
privileged: true
restart: always
ports:
- "5236:5236"
environment:
# Đường dẫn thư viện động
- LD_LIBRARY_PATH=/opt/dmdbms/bin
# Đặt bộ ký tự thành UTF-8
- UNICODE_FLAG=1
# Cấu hình phân biệt chữ hoa/thường cho tên bảng và cột (1: có, 0: không). Khuyên dùng 0.
- CASE_SENSITIVE=0
# Tên của instance cơ sở dữ liệu khi khởi tạo
- INSTANCE_NAME=dm8_instance
# Mật khẩu quản trị viên (SYSDBA), cần ít nhất 8 ký tự và không chứa ký tự đặc biệt cho phiên bản Docker
- SYSDBA_PWD=MyStrongDM8Pwd
volumes:
# Thư mục lưu trữ dữ liệu của database
- ./data:/opt/dmdbms/data
- /etc/localtime:/etc/localtime
Một điểm quan trọng cần lưu ý trong cấu hình docker-compose.yml là biến môi trường CASE_SENSITIVE. Việc đặt CASE_SENSITIVE=0 sẽ làm cho tên bảng và tên cột không phân biệt chữ hoa chữ thường, giúp đơn giản hóa việc viết truy vấn SQL. Mặc định, Dameng phân biệt chữ hoa chữ thường, yêu cầu bạn phải sử dụng dấu ngoặc kép cho tên bảng và cột nếu chúng không phải là chữ hoa hoàn toàn, điều này có thể gây bất tiện.
Cuối cùng, điều hướng đến thư mục chứa file docker-compose.yml và chạy lệnh sau để khởi động dịch vụ cơ sở dữ liệu Dameng:
docker-compose up -d
II. Kết Nối Cơ Sở Dữ Liệu Dameng Bằng Công Cụ Client
Để quản lý Dameng, bạn có thể tải xuống công cụ client cho Windows từ trang web chính thức của Dameng. Sau khi giải nén file cài đặt, chọn tùy chọn "Client Installation" (Cài đặt Client) để chỉ cài đặt các công cụ quản lý mà không cần cài đặt lại dịch vụ cơ sở dữ liệu trên máy tính Windows của bạn.
Sau khi hoàn tất cài đặt client, khởi động "DM Installation Tool" (Công cụ Cài đặt DM). Bạn sẽ cần cung cấp địa chỉ IP của máy chủ ảo (ví dụ: 192.168.136.128) và cổng (5236) mà vùng chứa Docker của Dameng đang lắng nghe. Đảm bảo điền thông tin đăng ký để lưu mật khẩu kết nối. Khi kết nối thành công, bạn có thể bắt đầu quản lý cơ sở dữ liệu.
Bạn có thể kiểm tra thông tin cấp phép, phiên bản và các instance đang chạy bằng các truy vấn SQL sau:
- Để kiểm tra thời gian sử dụng miễn phí:
SELECT * FROM SYS.V$LICENSE; - Để xem thông tin phiên bản và ngày xây dựng:
SELECT * FROM SYS.V$VERSION; - Để xem thông tin instance đã triển khai:
SELECT * FROM SYS.V$INSTANCE;
Để tối ưu hóa hiệu suất và quản lý, chúng ta nên tạo một tablespace và một người dùng riêng biệt cho ứng dụng. Sử dụng công cụ quản lý DM, tạo một tablespace mới, ví dụ: DM_APP_TS. Tiếp theo, tạo một người dùng mới, ví dụ: DM_USER với mật khẩu MyUserPwd123, và gán cho người dùng này tablespace mặc định là DM_APP_TS. Khi tạo một người dùng, Dameng sẽ tự động tạo một schema (lược đồ) tương ứng với tên người dùng đó. Bạn có thể coi schema này như một cơ sở dữ liệu logic riêng biệt.
Trong schema DM_USER, chúng ta sẽ tạo một bảng có tên là NhanVien để lưu trữ thông tin nhân viên. Dưới đây là câu lệnh SQL để tạo bảng:
CREATE TABLE "DM_USER"."NhanVien"
(
"id" INT NOT NULL,
"ho_ten" VARCHAR(50),
"so_dien_thoai" VARCHAR(20),
"thoi_gian_tao" DATETIME(0),
UNIQUE("id"),
NOT CLUSTER PRIMARY KEY("id")
) STORAGE(ON "DM_APP_TS", CLUSTERBTR);
III. Truy Cập Cơ Sở Dữ Liệu Bằng Ứng Dụng Spring Boot
Bây giờ, chúng ta sẽ xây dựng một ứng dụng Spring Boot đơn giản để kết nối và tương tác với cơ sở dữ liệu Dameng. Tạo một dự án Spring Boot mới, ví dụ: spring-dameng-integration.
Cấu hình Maven (pom.xml)
Thêm các dependency cần thiết vào file pom.xml của bạn. Đặc biệt, bạn cần thêm driver JDBC của Dameng (DmJdbcDriver18) và các thư viện khác như Mybatis-Plus và Lombok:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>spring-dameng-integration</artifactId>
<version>1.0.0-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.6</version> <!-- Sử dụng phiên bản Spring Boot phù hợp -->
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- Driver kết nối cơ sở dữ liệu Dameng -->
<dependency>
<groupId>com.dameng</groupId>
<artifactId>DmJdbcDriver18</artifactId>
<version>8.1.3.140</version> <!-- Thay đổi phiên bản nếu cần -->
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.0</version> <!-- Sử dụng phiên bản Mybatis-Plus phù hợp -->
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
Cấu hình Ứng dụng (application.yml)
Trong file src/main/resources/application.yml, cấu hình thông tin kết nối cơ sở dữ liệu Dameng:
server:
port: 8080 # Cổng của ứng dụng Spring Boot
spring:
datasource:
driver-class-name: dm.jdbc.driver.DmDriver
url: jdbc:dm://192.168.136.128:5236 # Thay đổi IP và cổng nếu cần
username: DM_USER # Tên người dùng đã tạo
password: MyUserPwd123 # Mật khẩu người dùng đã tạo
mybatis-plus:
configuration:
# Hiển thị câu lệnh SQL trong log để gỡ lỗi. KHÔNG NÊN BẬT trong môi trường sản xuất.
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
Cấu hình Mybatis-Plus cho Phân trang
Để hỗ trợ phân trang với Mybatis-Plus, tạo một lớp cấu hình:
package com.example.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfiguration {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor();
// Đặt kiểu cơ sở dữ liệu cho trình chặn phân trang là Dameng
paginationInterceptor.setDbType(DbType.DM);
interceptor.addInnerInterceptor(paginationInterceptor);
return interceptor;
}
}
Định nghĩa Entity, Mapper và Service
Tạo entity NhanVienEntity tương ứng với bảng NhanVien:
package com.example.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
// Chỉ định schema và tên bảng
@TableName(schema = "DM_USER", value = "NhanVien")
public class NhanVienEntity {
@TableId
@TableField("id")
private Long maNhanVien;
@TableField("ho_ten")
private String tenDayDu;
@TableField("so_dien_thoai")
private String sdtLienHe;
@TableField("thoi_gian_tao")
private LocalDateTime thoiGianTao;
}
Tạo interface NhanVienMapper kế thừa từ BaseMapper của Mybatis-Plus:
package com.example.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.entity.NhanVienEntity;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface NhanVienMapper extends BaseMapper<NhanVienEntity> {
}
Tạo lớp NhanVienService để xử lý logic nghiệp vụ:
package com.example.service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.entity.NhanVienEntity;
import com.example.mapper.NhanVienMapper;
import org.springframework.stereotype.Service;
@Service
public class NhanVienService extends ServiceImpl<NhanVienMapper, NhanVienEntity> {
}
Kiểm thử các thao tác CRUD và Phân trang
Để kiểm tra các chức năng thêm, đọc, cập nhật, xóa và phân trang, chúng ta có thể viết một lớp kiểm thử như sau:
package com.example;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.entity.NhanVienEntity;
import com.example.service.NhanVienService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@SpringBootTest
public class KiemThuThaoTacNhanVien {
@Autowired
private NhanVienService nhanVienService;
/**
* Lưu ý quan trọng: Khi triển khai Dameng DB bằng Docker, chúng tôi đã đặt CASE_SENSITIVE=0 (không phân biệt chữ hoa/thường).
* Điều này cho phép sử dụng tên bảng và tên cột bằng chữ thường hoặc chữ hoa.
* Nếu CASE_SENSITIVE=1 (mặc định), bạn PHẢI sử dụng tên bảng và cột bằng chữ hoa hoàn toàn hoặc đặt chúng trong dấu ngoặc kép.
*/
@Test
void kiemTraThemDuLieuNhanVien() {
nhanVienService.removeById(1L); // Xóa dữ liệu cũ nếu tồn tại
nhanVienService.removeById(2L);
nhanVienService.removeById(3L);
nhanVienService.removeById(4L);
nhanVienService.removeById(5L);
NhanVienEntity nv1 = new NhanVienEntity(1L, "Nguyen Van A", "0901234567", LocalDateTime.now().minusDays(5));
NhanVienEntity nv2 = new NhanVienEntity(2L, "Tran Thi B", "0902345678", LocalDateTime.now().minusDays(4));
NhanVienEntity nv3 = new NhanVienEntity(3L, "Le Van C", "0903456789", LocalDateTime.now().minusDays(3));
NhanVienEntity nv4 = new NhanVienEntity(4L, "Pham Thi D", "0904567890", LocalDateTime.now().minusDays(2));
NhanVienEntity nv5 = new NhanVienEntity(5L, "Hoang Van E", "0905678901", LocalDateTime.now().minusDays(1));
assertTrue(nhanVienService.save(nv1));
assertTrue(nhanVienService.save(nv2));
assertTrue(nhanVienService.save(nv3));
assertTrue(nhanVienService.save(nv4));
assertTrue(nhanVienService.save(nv5));
System.out.println("Thêm dữ liệu nhân viên thành công.");
}
@Test
void kiemTraTimKiemTheoMa() {
Long maCanTim = 2L;
NhanVienEntity nhanVien = nhanVienService.getById(maCanTim);
assertNotNull(nhanVien, "Không tìm thấy nhân viên với mã: " + maCanTim);
System.out.println("Tìm thấy nhân viên: " + nhanVien);
}
@Test
void kiemTraDocTatCaDuLieu() {
List<NhanVienEntity> danhSachNhanVien = nhanVienService.list();
assertFalse(CollectionUtils.isEmpty(danhSachNhanVien), "Không có dữ liệu nhân viên nào.");
System.out.println("Danh sách nhân viên:");
danhSachNhanVien.forEach(System.out::println);
}
@Test
void kiemTraPhanTrangDuLieu() {
long trangHienTai = 2; // Bắt đầu từ trang 1
long soBanGhiTrenTrang = 2;
Page<NhanVienEntity> cauHinhTrang = new Page<>(trangHienTai, soBanGhiTrenTrang);
LambdaQueryWrapper<NhanVienEntity> dieuKienTruyVan = new LambdaQueryWrapper<>();
dieuKienTruyVan.orderByAsc(NhanVienEntity::getMaNhanVien);
Page<NhanVienEntity> ketQuaPhanTrang = nhanVienService.page(cauHinhTrang, dieuKienTruyVan);
List<NhanVienEntity> banGhiTrenTrang = ketQuaPhanTrang.getRecords();
assertFalse(CollectionUtils.isEmpty(banGhiTrenTrang), "Trang này không có dữ liệu.");
System.out.println("Dữ liệu trang " + trangHienTai + ":");
banGhiTrenTrang.forEach(System.out::println);
System.out.println("Tổng số bản ghi: " + ketQuaPhanTrang.getTotal());
System.out.println("Tổng số trang: " + ketQuaPhanTrang.getPages());
}
@Test
void kiemTraCapNhatDuLieu() {
Long maNhanVienCapNhat = 2L;
NhanVienEntity nhanVienCanCapNhat = nhanVienService.getById(maNhanVienCapNhat);
assertNotNull(nhanVienCanCapNhat, "Không tìm thấy nhân viên để cập nhật: " + maNhanVienCapNhat);
nhanVienCanCapNhat.setTenDayDu("Tran Thi B (Đã Cập Nhật)");
nhanVienCanCapNhat.setSdtLienHe("0987654321");
assertTrue(nhanVienService.updateById(nhanVienCanCapCapNhat));
System.out.println("Cập nhật nhân viên thành công: " + nhanVienService.getById(maNhanVienCapNhat));
}
@Test
void kiemTraXoaDuLieu() {
Long maNhanVienXoa = 4L;
assertTrue(nhanVienService.removeById(maNhanVienXoa));
System.out.println("Xóa nhân viên thành công với mã: " + maNhanVienXoa);
NhanVienEntity nhanVienDaXoa = nhanVienService.getById(maNhanVienXoa);
assertTrue(Objects.isNull(nhanVienDaXoa));
}
}