SpringBoot項(xiàng)目統(tǒng)一枚舉轉(zhuǎn)換實(shí)踐過程
SpringBoot 項(xiàng)目統(tǒng)一枚舉轉(zhuǎn)換實(shí)踐
1 現(xiàn)有問題
目前的項(xiàng)目中,有些枚舉字段,在傳遞的時(shí)候,需要經(jīng)常對(duì)枚舉進(jìn)行對(duì)應(yīng)的轉(zhuǎn)換,有如下場(chǎng)景:
- 存儲(chǔ)進(jìn)數(shù)據(jù)庫的時(shí)候,需要存儲(chǔ)為 int;
- 查詢出來的時(shí)候,需要對(duì)該數(shù)值進(jìn)行轉(zhuǎn)換;
- 接收前端參數(shù)的時(shí)候,需要將數(shù)字轉(zhuǎn)換為我們系統(tǒng)的枚舉;
- 響應(yīng)的參數(shù)包含枚舉的時(shí)候,需要將枚舉轉(zhuǎn)換成 int;
- 發(fā)送或接收 MQ 消息時(shí),又得對(duì)枚舉進(jìn)行轉(zhuǎn)換。
可以看到,我們?cè)谙到y(tǒng)中需要做大量的枚舉轉(zhuǎn)換工作,那么是不是有什么方法對(duì)枚舉轉(zhuǎn)換進(jìn)行簡化呢?
2 數(shù)據(jù)庫轉(zhuǎn)換枚舉
我們這邊的系統(tǒng)使用的持久層框架是 Spring Data jpa,底層實(shí)現(xiàn)是 Hibernate,對(duì)于數(shù)據(jù)庫枚舉的轉(zhuǎn)化提供了 AttributeConverter 接口,可以實(shí)現(xiàn)枚舉和 int 自動(dòng)轉(zhuǎn)換。
例如我們有一個(gè)枚舉類
@Getter
@RequiredArgsConstructor
public enum SexEnum {
/**
* 1 男
*/
MALE(1, "男"),
/**
* 2 女
*/
FEMALE(2, "女"),
;
private final Integer value;
}那么,我們就可以實(shí)現(xiàn) AttributeConverter 接口,來實(shí)現(xiàn)枚舉的自動(dòng)轉(zhuǎn)換。
@Converter(autoApply = true)
public class SexEnumConverter implements AttributeConverter<SexEnum, Integer> {
@Override
public Integer convertToDatabaseColumn(SexEnum attribute) {
return attribute == null ? null : attribute.getValue();
}
@Override
public SexEnum convertToEntityAttribute(Integer dbData) {
return Arrays.stream(SexEnum.class)
.filter(sexEnum -> sexEnum.getValue().equals(dbData))
.findFirst()
.orElse(null);
}
}但是這樣子每一個(gè)枚舉類又必須寫對(duì)應(yīng)的枚舉轉(zhuǎn)換的代碼,很麻煩,因此,接下來進(jìn)行優(yōu)化。
我們可以先定義一個(gè)父類的枚舉接口
public interface IEnum {
Integer getValue();
}然后優(yōu)化我們的枚舉轉(zhuǎn)換類
public class EnumConverter<E extends IEnum> implements AttributeConverter<E, Integer> {
private final Class<E> clazz;
@SuppressWarnings("unchecked")
public EnumConverter() {
this.clazz = (Class<E>) (((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments())[0];
}
@Override
public Integer convertToDatabaseColumn(E attribute) {
return Optional.ofNullable(attribute)
.map(IEnum::getValue)
.orElse(null);
}
@Override
public E convertToEntityAttribute(Integer dbData) {
return Arrays.stream(clazz.getEnumConstants())
.filter(iEnum -> iEnum.getValue().equals(dbData))
.findFirst()
.orElse(null);
}
}然后枚舉類繼承我們的父類枚舉接口,再定義一個(gè)子類實(shí)現(xiàn),就可以實(shí)現(xiàn)枚舉的自動(dòng)轉(zhuǎn)換
@Getter
@RequiredArgsConstructor
public enum SexEnum implements IEnum {
/**
* 1 男
*/
MALE(1, "男"),
/**
* 2 女
*/
FEMALE(2, "女"),
;
private final Integer value;
@Converter(autoApply = true)
public static class SexEnumConverter extends EnumConverter<SexEnum> {
}
}這樣子的話,對(duì)于數(shù)據(jù)庫的查詢或者保存,就可以直接使用枚舉進(jìn)行操作,而不用再去手動(dòng)轉(zhuǎn)換。
3 請(qǐng)求參數(shù)轉(zhuǎn)換枚舉
3.1 @Param 參數(shù)轉(zhuǎn)換成枚舉
對(duì)于請(qǐng)求參數(shù),我們分為兩種,先看第一種,攜帶在 URL 后面的參數(shù)。
spring 提供了一個(gè) ConverterFactory 接口,我們通過實(shí)現(xiàn)他,便可以實(shí)現(xiàn)對(duì)枚舉的自動(dòng)轉(zhuǎn)換,這里同樣要使用到我們的父類接口 IEnum。
@Component
public class IEnumConverterFactory implements ConverterFactory<String, IEnum> {
@Override
public <T extends IEnum> Converter<String, T> getConverter(Class<T> targetType) {
return source ->
Arrays.stream(targetType.getEnumConstants())
.filter(x -> x.getValue().toString().equals(source))
.findFirst()
.orElse(null);
}
}然后實(shí)現(xiàn) WebMvcConfigurer,添加該類型轉(zhuǎn)換器
@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {
private final IEnumConverterFactory iEnumConverterFactory;
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverterFactory(iEnumConverterFactory);
}
}然后我們便可以在在方法上使用枚舉,MVC 框架會(huì)自動(dòng)幫我們把數(shù)值轉(zhuǎn)成對(duì)應(yīng)的枚舉。
3.2 @RequestBody 參數(shù)轉(zhuǎn)換成枚舉
使用 @RequestBody 接收請(qǐng)求參數(shù),底層其實(shí)是使用了 Jackson 的反序列化功能。
我們可以繼承 Jackson 的 JsonDeserializer 抽象類,自定義我們的反序列化策略。
public class IEnumDeserializer extends JsonDeserializer<IEnum> implements ContextualDeserializer {
private Class<? extends IEnum> clazz;
public IEnumDeserializer() {
}
public IEnumDeserializer(Class<? extends IEnum> clazz) {
this.clazz = clazz;
}
@SneakyThrows
@Override
public IEnum deserialize(JsonParser jsonParser, DeserializationContext ctxt) {
final String param = jsonParser.getText();
return Arrays.stream(clazz.getEnumConstants())
.filter(x -> x.getValue().toString().equals(param))
.findFirst()
.orElse(null);
}
@SuppressWarnings({"unchecked"})
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
JavaType type = property.getType();
// 如果是容器,則返回容器內(nèi)部枚舉類型
while (type.isContainerType()) {
type = type.getContentType();
}
return new IEnumDeserializer((Class<? extends IEnum>) type.getRawClass());
}
}然后在我們的 IEnum 類上,添加 @JsonDeserialize 注解,指定反序列化。
@JsonDeserialize(using = IEnumDeserializer.class)
public interface IEnum {
Integer getValue();
}這樣子,就可以在 @RequestBody 注解的實(shí)體類里面使用枚舉。
4 響應(yīng)參數(shù)轉(zhuǎn)換枚舉
我們返回響應(yīng)參數(shù)時(shí),經(jīng)常需要把枚舉值轉(zhuǎn)成對(duì)應(yīng)的 int,因?yàn)镾pring boot框架響應(yīng)參數(shù)的時(shí)候,采用的也是 Jackson 序列化,因此我們可以采用類似上面的做法,繼承 JsonSerializer 抽象類,實(shí)現(xiàn)自定義轉(zhuǎn)換。
public class IEnumSerializer extends JsonSerializer<IEnum> {
@Override
public void serialize(IEnum value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (Objects.isNull(value)) {
gen.writeNull();
return;
}
gen.writeNumber(value.getValue());
}
}然后在我們的 IEnum 類上,添加 @JsonDeserialize 注解,指定序列化。
@JsonSerialize(using = IEnumSerializer.class)
@JsonDeserialize(using = IEnumDeserializer.class)
public interface IEnum {
Integer getValue();
}這樣子,我們?cè)诮o前端返回枚舉的時(shí)候,就可以不用自己手動(dòng)進(jìn)行轉(zhuǎn)換,讓框架自動(dòng)幫我們進(jìn)行轉(zhuǎn)換。
5 消息參數(shù)轉(zhuǎn)換枚舉
其實(shí)通過我們上面的配置,已經(jīng)自定義了枚舉的序列化和反序列化,只需要在我們的消息接收和發(fā)送的時(shí)候,采用 Jackson 序列化的形式,就可以實(shí)現(xiàn)枚舉的自動(dòng)轉(zhuǎn)換。
總結(jié)
通過以上的配置,基本上可以實(shí)現(xiàn)我們整個(gè)項(xiàng)目的枚舉統(tǒng)一,消除我們?cè)陧?xiàng)目內(nèi)頻繁需要對(duì)枚舉和數(shù)值互相轉(zhuǎn)換的操作。
如果是前端頁面有相關(guān)的枚舉下拉框選擇查詢,我們還可以根據(jù)頂層的枚舉接口,對(duì)所有枚舉進(jìn)行掃描并存儲(chǔ)到內(nèi)存中,然后提供枚舉查詢的接口給前端,這樣子的話,后續(xù)新增枚舉參數(shù)的話,可以直接就從接口獲取到更新的值。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
如何解決Spring in action @valid驗(yàn)證不生效的問題
這篇文章主要介紹了如何解決Spring in action @valid驗(yàn)證不生效的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06
springboot項(xiàng)目配置多個(gè)kafka的示例代碼
這篇文章主要介紹了springboot項(xiàng)目配置多個(gè)kafka,本文通過示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-04-04
java面向?qū)ο笤O(shè)計(jì)原則之開閉原則示例解析
這篇文章主要介紹了java面向?qū)ο笤O(shè)計(jì)原則之開閉原則的示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2021-10-10
Intellij Idea中進(jìn)行Mybatis逆向工程的實(shí)現(xiàn)
這篇文章主要介紹了Intellij Idea中進(jìn)行Mybatis逆向工程的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05
java多次嵌套循環(huán)查詢數(shù)據(jù)庫導(dǎo)致代碼中數(shù)據(jù)處理慢的解決
這篇文章主要介紹了java多次嵌套循環(huán)查詢數(shù)據(jù)庫導(dǎo)致代碼中數(shù)據(jù)處理慢的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
java實(shí)現(xiàn)上傳文件到oss(阿里云)功能示例
這篇文章主要介紹了java實(shí)現(xiàn)上傳文件到oss(阿里云)功能,結(jié)合實(shí)例形式詳細(xì)分析了java上傳文件到阿里云的具體步驟、配置及相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2019-11-11
深入理解Java中的volatile關(guān)鍵字(總結(jié)篇)
volatile這個(gè)關(guān)鍵字,不僅僅在Java語言中有,在很多語言中都有的,而且其用法和語義也都是不盡相同的。這篇文章主要介紹了Java中的volatile關(guān)鍵字,需要的朋友可以參考下2018-10-10

