Apache Derby Bài 39: Xử lý dữ liệu thời gian (DATE, TIME, TIMESTAMP) và bài toán Timezone trong Derby
1. Ba kiểu dữ liệu thời gian cốt lõi
Apache Derby tuân thủ chặt chẽ tiêu chuẩn SQL cho việc lưu trữ dữ liệu thời gian thông qua 3 kiểu dữ liệu riêng biệt. Việc chọn đúng kiểu dữ liệu không chỉ giúp tiết kiệm không gian lưu trữ mà còn làm tăng tính rõ ràng cho thiết kế CSDL.
-
DATE(Ngày): Lưu trữ ngày, tháng, năm. Bỏ qua hoàn toàn giờ phút giây.-
Định dạng chuẩn:
YYYY-MM-DD(Ví dụ:2026-10-10) -
Ứng dụng: Ngày sinh, ngày hết hạn thẻ, ngày lễ.
-
-
TIME(Giờ): Chỉ lưu trữ giờ, phút, giây. Bỏ qua ngày tháng.-
Định dạng chuẩn:
HH:MM:SS(Ví dụ:10:59:08) -
Ứng dụng: Cấu hình giờ báo thức, giờ bắt đầu ca làm việc.
-
-
TIMESTAMP(Dấu thời gian): Kết hợp cả DATE và TIME, đồng thời hỗ trợ độ chính xác lên đến nano-giây (thường là 6 đến 9 chữ số thập phân).-
Định dạng chuẩn:
YYYY-MM-DD HH:MM:SS.NNNNNN -
Ứng dụng: Ghi log hệ thống (
created_at,updated_at), thời điểm thanh toán thành công.
-
Cú pháp tạo bảng:
SQL
CREATE TABLE phien_giao_dich (
ma_phien INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
ngay_giao_dich DATE,
gio_khop_lenh TIME,
thoi_diem_hoan_tat TIMESTAMP
);
2. Các hàm thời gian nội tại (Built-in Functions)
Trong các lệnh INSERT hoặc UPDATE, thay vì phải gửi chuỗi thời gian từ Java xuống, bạn có thể yêu cầu Derby tự động lấy thời gian hiện tại của máy chủ bằng các hàm nội tại:
-
CURRENT_DATE(hoặcCURRENT DATE) -
CURRENT_TIME(hoặcCURRENT TIME) -
CURRENT_TIMESTAMP(hoặcCURRENT TIMESTAMP)
SQL
-- Cập nhật thời điểm sửa đổi mới nhất bằng hàm của Derby Engine
UPDATE don_hang
SET trang_thai = 'DA_XU_LY', updated_at = CURRENT_TIMESTAMP
WHERE id = 105;
3. Nút thắt cổ chai: Bài toán Múi giờ (Timezone)
Khác với PostgreSQL (hỗ trợ kiểu TIMESTAMP WITH TIME ZONE), Apache Derby KHÔNG lưu trữ thông tin múi giờ kèm theo dữ liệu thời gian. Khi bạn lưu chuỗi 2026-10-10 10:59:00 vào cột TIMESTAMP, Derby chỉ lưu đúng các con số đó dưới dạng Local Time (Thời gian cục bộ).
Điều này dẫn đến một sự cố kinh điển trong các hệ thống phân tán:
-
Máy chủ CSDL Derby đặt tại Nhật Bản (Múi giờ GMT+9).
-
Server Web Java đặt tại Việt Nam (Múi giờ GMT+7).
-
Server Web gọi hàm
CURRENT_TIMESTAMPcủa Derby, kết quả ghi vào CSDL là12:59(Giờ Nhật Bản). Nhưng khi người dùng tại Việt Nam đọc lên, ứng dụng lại không biết đó là giờ Nhật, nên hiển thị thẳng12:59, khiến dữ liệu bị sai lệch 2 tiếng.
4. Best Practice: Nguyên tắc "Lưu trữ UTC - Hiển thị Local"
Để giải quyết triệt để rắc rối về Timezone, mọi hệ thống Backend hiện đại đều phải tuân thủ một nguyên tắc duy nhất: Toàn bộ dữ liệu TIMESTAMP trong cơ sở dữ liệu phải được ép về chuẩn UTC (GMT+0). Múi giờ (như Asia/Saigon hay America/New_York) chỉ được áp dụng ở tầng Application (Java/Frontend) khi cần hiển thị cho người dùng.
Để làm được điều này, chúng ta cần phối hợp cấu hình JVM và sử dụng API thời gian hiện đại của Java 8 (java.time).
Bước 1: Ép JVM của máy chủ chạy ở múi giờ UTC
Khi khởi động ứng dụng Java (hoặc Derby Network Server), hãy thêm cờ (flag) này vào lệnh khởi động:
Bash
java -Duser.timezone=UTC -jar my-application.jar
Lúc này, mọi hàm CURRENT_TIMESTAMP của Derby sẽ tự động trả về giờ UTC chuẩn quốc tế.
Bước 2: Tương tác qua JDBC với java.time.*
Trước JDBC 4.2, lập trình viên phải dùng lớp java.sql.Timestamp kỹ thuật số rất rườm rà. Hiện nay, bạn nên dùng trực tiếp LocalDateTime (chứa ngày giờ) hoặc ZonedDateTime của Java 8 và đẩy qua hàm setObject().
Mã nguồn chèn và đọc thời gian bằng Java:
Java
package com.hieu.derby.datetime;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
public class DerbyTimezoneDemo {
public static void main(String[] args) {
String url = "jdbc:derby://localhost:1527/log_db;create=true";
try (Connection conn = DriverManager.getConnection(url)) {
// 1. Tạo dữ liệu thời gian chuẩn UTC tại tầng Java
ZonedDateTime nowInVietnam = ZonedDateTime.now(ZoneId.of("Asia/Saigon"));
// Ép về UTC trước khi ghi xuống DB
LocalDateTime utcTimeToSave = nowInVietnam.withZoneSameInstant(ZoneId.of("UTC")).toLocalDateTime();
// 2. Ghi xuống Derby bằng setObject()
String insertSql = "INSERT INTO nhat_ky_he_thong (hanh_dong, thoi_gian) VALUES (?, ?)";
try (PreparedStatement pstmt = conn.prepareStatement(insertSql)) {
pstmt.setString(1, "USER_LOGIN");
pstmt.setObject(2, utcTimeToSave); // JDBC 4.2 tự động map LocalDateTime vào TIMESTAMP
pstmt.executeUpdate();
System.out.println("=> Đã lưu sự kiện vào CSDL với mốc thời gian UTC: " + utcTimeToSave);
}
// 3. Đọc từ Derby và chuyển ngược về múi giờ Việt Nam
String selectSql = "SELECT hanh_dong, thoi_gian FROM nhat_ky_he_thong";
try (PreparedStatement pstmt = conn.prepareStatement(selectSql);
ResultSet rs = pstmt.executeQuery()) {
if (rs.next()) {
// Đọc nguyên bản từ DB (hiểu ngầm là UTC do quy ước thiết kế)
LocalDateTime dbTime = rs.getObject("thoi_gian", LocalDateTime.class);
// Gắn múi giờ UTC vào thời gian vừa đọc
ZonedDateTime utcZonedTime = dbTime.atZone(ZoneId.of("UTC"));
// Chuyển đổi sang múi giờ hiển thị cho khách hàng (Việt Nam)
ZonedDateTime vietnamDisplayTime = utcZonedTime.withZoneSameInstant(ZoneId.of("Asia/Saigon"));
System.out.println("=> Thời gian hiển thị cho user (GMT+7): " + vietnamDisplayTime);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
5. Các hàm thao tác thời gian nội tại
Nếu bạn cần tính toán logic thời gian ngay trong câu truy vấn SQL (Ví dụ: Tìm các đơn hàng được tạo trong 7 ngày qua), Derby cung cấp các hàm chuyên dụng:
-
YEAR(date)/MONTH(date)/DAY(date)/HOUR(time): Trích xuất một thành phần cụ thể. -
Toán tử khoảng thời gian (Interval): Derby không có hàm
DATEADDhayDATEDIFFchuẩn mực như SQL Server, nhưng bạn có thể dùng cú pháp{fn TIMESTAMPADD(...)}của chuẩn JDBC Escape syntax.
Ví dụ: Lấy các đơn hàng đã tạo quá 30 ngày.
SQL
SELECT * FROM don_hang
WHERE thoi_diem_hoan_tat < {fn TIMESTAMPADD(SQL_TSI_DAY, -30, CURRENT_TIMESTAMP)};
(Cú pháp {fn ...} sẽ được JDBC Driver của Derby tự động biên dịch thành mã nội bộ tương ứng trước khi gửi xuống Engine).
Như vậy, để hệ thống hoạt động ổn định và nhất quán về mặt dữ liệu không gian - thời gian, bạn cần kỷ luật áp dụng chuẩn UTC tại tầng CSDL. Ở bài tiếp theo, chúng ta sẽ quay trở lại với một kỹ thuật tối ưu hóa hiệu năng cực kỳ quan trọng đã được nhắc đến ở Bài 26: Apache Derby Bài 40: Quản lý Connection Pool với thư viện HikariCP.
All Rights Reserved