Sinh converter cho Enum
govex-cloud-apt là annotation processor sinh converter cho enum tại compile-time: JPA AttributeConverter (lưu DB) và Spring Converter (binding request), kể cả biến thể cho List. Thêm vào build khi service có nhiều enum cần chuyển đổi và muốn loại bỏ code viết tay.
Khi nào sử dụng
- Enum của service cần lưu xuống database theo giá trị code thay vì ordinal/name.
- Enum cần binding tự động từ request param/path variable dạng chuỗi.
- Entity có field
List<Enum>muốn lưu dưới dạng chuỗi phân cách dấu phẩy. - Muốn converter được sinh và cập nhật tự động mỗi lần đổi enum, không phải bảo trì thủ công.
Cài đặt
Khai báo dependency (không khai version — version do BOM quản lý, xem Cài đặt):
<dependency>
<groupId>vn.govex.cloud</groupId>
<artifactId>govex-cloud-apt</artifactId>
<scope>provided</scope>
</dependency>
Điều kiện tiên quyết: govex-cloud-parent đã khai báo sẵn danh sách annotationProcessorPaths (lombok, spring-boot-configuration-processor). Khi annotationProcessorPaths được cấu hình, javac chỉ chạy processor trong danh sách đó — vì vậy bắt buộc khai báo thêm dependency trong annotationProcessorPaths của maven-compiler-plugin:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<!-- giữ các path có sẵn từ parent (lombok, spring-boot-configuration-processor) -->
<path>
<groupId>vn.govex.cloud</groupId>
<artifactId>govex-cloud-apt</artifactId>
<version>${govex-cloud.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
annotationProcessorPaths không tự lấy version từ BOM; khai version khớp BOM đang dùng bằng property trong pom.xml (ví dụ govex-cloud.version).
Cấu hình
Toàn bộ điều khiển qua annotation trên enum:
| Annotation / thuộc tính | Mô tả | Mặc định |
|---|---|---|
@EnumJpaConverter | Sinh {Enum}AttributeConverter (JPA, @Converter(autoApply = true)) | — |
@EnumJpaConverter.list | Sinh thêm {Enum}ListAttributeConverter cho List<Enum> (lưu chuỗi phân cách dấu phẩy) | false |
@EnumJpaConverter.packageName | Package chứa class sinh ra | package của enum |
@StringToEnumConverter | Sinh StringTo{Enum}Converter (Spring Converter<String, Enum>, @Component) | — |
@StringToEnumConverter.list | Sinh thêm StringToList{Enum}Converter | true |
@StringToEnumConverter.packageName | Package chứa class sinh ra | package của enum |
Sử dụng
Chuẩn bị enum có value type xác định
Enum cần xác định được giá trị đại diện qua CodeEnum<T> hoặc @JsonValue + @JsonCreator:
@EnumJpaConverter(list = true)
@StringToEnumConverter
public enum Status implements CodeEnum<Integer> {
ACTIVE(1, "Hoạt động"),
INACTIVE(0, "Không hoạt động");
private final Integer code;
private final String name;
Status(Integer code, String name) {
this.code = code;
this.name = name;
}
@Override
public Integer getCode() {
return code;
}
@Override
public String getName() {
return name;
}
}
Kết quả sinh ra
Trong target/generated-sources/annotations/ (cùng package với enum nếu không ghi đè):
StatusAttributeConverter— JPAAttributeConverter<Status, Integer>,@Converter(autoApply = true).StatusListAttributeConverter— JPAAttributeConverter<List<Status>, String>(dolist = true).StringToStatusConverter— SpringConverter<String, Status>, gắn@Component.StringToListStatusConverter— SpringConverter<String, List<Status>>(dolistmặc địnhtrue).
Đổi package converter
@EnumJpaConverter(packageName = "vn.govex.app.converter")
public enum Status implements CodeEnum<Integer> {
// ...
}
Output sinh ra
Ví dụ với enum Status ở trên, processor ghi 4 file vào target/generated-sources/annotations/ (cùng package với enum nếu không ghi đè packageName; một số import java.lang/Override được lược bớt cho gọn).
StatusAttributeConverter.java — JPA converter cho một giá trị:
// This codes are generated automatically. Do not modify!
package vn.govex.app.enums;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
@Converter(autoApply = true)
public final class StatusAttributeConverter implements AttributeConverter<Status, Integer> {
@Override
public Integer convertToDatabaseColumn(Status i) {
return i == null ? null : i.getCode();
}
@Override
public Status convertToEntityAttribute(Integer i) {
if (i == null) return null;
for (Status value : Status.values()) {
if (value.getCode().equals(i)) {
return value;
}
}
return null;
}
}
StatusListAttributeConverter.java — JPA converter cho List<Status>, lưu chuỗi phân cách dấu phẩy:
// This codes are generated automatically. Do not modify!
package vn.govex.app.enums;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
@Converter(autoApply = true)
public final class StatusListAttributeConverter implements AttributeConverter<List<Status>, String> {
@Override
public String convertToDatabaseColumn(List<Status> list) {
if (CollectionUtils.isEmpty(list)) {
return null;
}
return list.stream().map(Status::getCode).map(Objects::toString).collect(Collectors.joining(","));
}
@Override
public List<Status> convertToEntityAttribute(String sources) {
if (!StringUtils.hasText(sources)) {
return null;
}
List<Status> list = new ArrayList<>();
for (String source : sources.split(",")) {
if (StringUtils.hasText(source)) {
for (Status value : Status.values()) {
if (source.equals(value.getCode().toString())) {
list.add(value);
}
}
}
}
return list;
}
}
StringToStatusConverter.java — Spring Converter<String, Status> dùng cho binding request:
// This codes are generated automatically. Do not modify!
package vn.govex.app.enums;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationContext;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
@Component
@RequiredArgsConstructor
public final class StringToStatusConverter implements Converter<String, Status> {
private final ApplicationContext context;
@Override
public Status convert(String source) {
if (!StringUtils.hasText(source)) return null;
for (Status value : Status.values()) {
if (source.equals(value.getCode().toString())) {
return value;
}
}
return null;
}
}
StringToListStatusConverter.java — Spring Converter<String, List<Status>> (do list mặc định true):
// This codes are generated automatically. Do not modify!
package vn.govex.app.enums;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationContext;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.List;
@Component
@RequiredArgsConstructor
public final class StringToListStatusConverter implements Converter<String, List<Status>> {
private final ApplicationContext context;
@Override
public List<Status> convert(String sources) {
if (!StringUtils.hasText(sources)) {
return null;
}
List<Status> list = new ArrayList<>();
for (String source : sources.split(",")) {
if (StringUtils.hasText(source)) {
for (Status value : Status.values()) {
if (source.equals(value.getCode().toString())) {
list.add(value);
}
}
}
}
return list;
}
}
Khi @JsonCreator nhận tham số String, converter sinh ra gọi trực tiếp method đó; khi tham số không phải String, code dùng context.getBean(ConversionService.class).convert(source, ...) như mô tả ở mục Lưu ý.
Lưu ý
- Processor tự phát hiện value type theo thứ tự:
@JsonValue→CodeEnum<T>→ tham số đầu tiên của@JsonCreator; không xác định được sẽ báo lỗi ngay lúc biên dịch. - JPA converter dùng
autoApply = truenên tự áp dụng cho mọi entity field cùng kiểu, không cần khai báo@Convert. - Spring converter được đánh
@Component: package chứa nó phải nằm trong phạm vi component-scan — cẩn thận khi ghi đèpackageNamesang package chưa được scan. - Khi
@JsonCreatornhận tham số không phảiString, converter sinh ra dùngConversionServiceđể chuyển trung gian (String→Integer→ enum); ứng dụng cần cóConversionServicetương ứng. - Dependency hỗ trợ Java 17+ (
SourceVersion.RELEASE_17); file sinh ra có ghi chú không được sửa tay — mọi thay đổi nằm ở enum hoặc annotation.