Hôm nọ trong lúc làm Job đồng bộ 5.2M triệu dữ liệu khách hàng, mình có gặp 1 issue nhỏ liên quan đến việc sử dụng Mapstruct để mapping dữ liệu qua lại, qua đó có cái nhìn rõ ràng hơn về cách Mapstruct mapping các trường dữ liệu dạng LocalDateTime.
Tổng quát hoá vấn đề như này, mình có hai bảng dữ liệu:
Bảng 1 là bảng nguồn, lưu trữ thông tin khách hàng (lượng dữ liệu lớn) và có trường
verify_account_atám chỉ là thời điểm khách hàng xác thực, được lưu dạng Timestamp trong OracleBảng 2 là bảng lưu trữ thông tin khách hàng đồng ý điều kiện điều khoản, có trường
agreed_atám chỉ là thời điểm khách hàng xác nhận đồng ý điều kiện điều khoản, trường này sẽ lấy giá trị từ Bảng 1 để lưu vào với từng khách hàng (kiểu dữ liệu cũng là Timestampt trong Oracle)
Nghe chừng thì đơn giản lắm luôn, chỉ lấy ra và lưu lại thôi thì vấn đề là gì?
Sau khi nhìn sơ qua về dữ liệu, thấy chênh lệch 7 tiếng giữa hai trường verify_account_at và agreed_at thì mình phán đoán ngay là có sự chênh lệch của múi giờ, giữa giờ UTC+7 (ví dụ: giờ Việt Nam – ICT) và UTC (Giờ Phối hợp Quốc tế).
Nhưng quan trọng là việc lệch ở đây sẽ xuất phát ở bước nào?, chúng ta sẽ cần phải tìm hiểu chi tiết, có thể là từ bước lấy ra dữ liệu trong database nguồn ở bảng 1, hoặc bước ghi nhận dữ liệu vào database đích ở bảng 2, hoặc trong quá trình bắn event qua lại giữa hai Process gây ra chênh lệch,…
Trước tiên phân tích Step 1, chúng ta lấy ra cột verify_account_at ở bảng 1, cột này có kiểu dữ liệu là TIMESTAMP (không có thông tin múi giờ) trong Oracle, nó đang lưu giá trị là giờ địa phương (local time), ví dụ: 2025-06-25 16:38:33.728000.
Ở Step 1 này, sau khi lấy ra được đối tượng khách hàng cụ thể, có trường verify_account_at kiểu LocalDateTime (kiểu dữ liệu này chỉ biểu diễn ngày và giờ, ví dụ “16:38:33 ngày 25-06-2025”, mà không biết đó là 16:38 ở Hà Nội, London hay New York,…)
Trước khi phân tích Step 2 ở việc lưu dữ liệu, thì ở bước giữa Step 1 và Step 2 có 1 đoạn mapping dữ liệu bằng Mapstruct khi bắn event qua hai process khác nhau, các bạn xem hình ảnh bên dưới để hiểu thêm flow nhé.
Process ở bước 1 sẽ scan toàn bộ tài khoản customer, trong từng thread sẽ xử lý một vài nghiệp vụ logic, mapping lại data customer bằng Mapstruct, sau đó bắn event vào queue ở bước 2, Process ở bước 2 sẽ lấy ra và xử lý thêm ít logic, rồi lưu vào database.
Giờ chúng ta sẽ tiến hành debug một chút xem lỗi nằm ở bước nào:
Tới đoạn này chúng ta sẽ Evaluate một chút xem giá trị lấy ra từ hàm getVerifyAccountAt đang trả dữ liệu như nào nhé.
Tới đoạn này chúng ta đã nhìn ra vấn đề rồi, đoạn code .toInstant()ở trong Mapstruct gây ra sai khác thời gian, nhưng tại sao nó lại gây ra điều này?
if ( iCustomerViewDto.getVerifyAccountAt() != null ) {
customerDto.setVerifyAccountAt( LocalDateTime.ofInstant( iCustomerViewDto.getVerifyAccountAt().toInstant(), ZoneId.of( "UTC" ) ) );
}Phương thức iCustomerViewDto.getVerifyAccountAt() trả về một đối tượng Timestamp, ở đây có giá trị là 2025-06-25 16:38:33.728.
Khi gọi .toInstant(), Java hiểu rằng thời điểm này đang ở múi giờ mặc định của JVM (ở đây là UTC+7) thì kết quả sẽ được chuyển đổi sang múi giờ UTC.
Cụ thể, thời điểm: 2025-06-25 16:38:33.728 ở UTC+7 → tương đương với 2025-06-25 09:38:33.728 ở UTC.
Kết quả của .toInstant() là Instant mang giá trị 2025-06-25T09:38:33.728Z
Tiếp tục, khi dùng LocalDateTime.ofInstant(..., ZoneId.of("UTC")), Java tạo ra một đối tượng LocalDateTime từ thời điểm 09:38:33.728 UTC, nên kết quả cuối cùng thu được sẽ là:2025-06-25T09:38:33.728
Phía trên là event sau khi trải qua quá trình quét và mapping dữ liệu.
Và đây là dữ liệu agreements trước khi chèn vào database bảng 2 đang ghi nhận thời gian UTC.
Phương án xử lý tốt nhất hiện tại đó là dạy MapStruct cách chuyển đổi đúng, bằng việc viết thêm một hàm default trong mapper
Mở file CustomerMapper.java
Thêm một default method để chuyển từ Timestamp sang LocalDateTime, logic đúng đơn giản là gọi
.toLocalDateTime()
@Mapping(source = "verifyAccountAt", target = "verifyAccountAt", qualifiedByName = "mappingTime")
CustomerDto mapCustomerToDto(ICustomerViewDto iCustomerViewDto);
List<CustomerDto> from(List<ICustomerViewDto> views);
@Named("mappingTime")
default LocalDateTime mappingTime(Timestamp timestamp) {
if (timestamp == null) {
return null;
}
return timestamp.toLocalDateTime();
}Oke giờ chúng ta sẽ debug lại và xem kết quả cuối cùng
timestamp.toLocalDateTime() chỉ đơn thuần chuyển đổi các thành phần ngày giờ trực tiếp mà không quan tâm múi giờ.
Dữ liệu đã hoàn toàn chính xác ở bảng đích, giá trị trường agreed_at đã lưu bằng với verify_account_at ở bảng nguồn, vấn đề đã được giải quyết, Perfect Money
The post Xử lý lỗi lệch múi giờ khi dùng Mapstruct appeared first on Cafeincode.
No posts

Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.