最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

spring?mybatis環(huán)境常量與枚舉轉(zhuǎn)換示例詳解

 更新時(shí)間:2023年06月02日 08:49:10   作者:Mzoro  
這篇文章主要為大家介紹了spring?mybatis環(huán)境常量與枚舉轉(zhuǎn)換示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

常量與枚舉對(duì)象的轉(zhuǎn)換

日常 web 接口開發(fā)中經(jīng)常遇到一些常量與枚舉對(duì)象的轉(zhuǎn)換,如:請(qǐng)求中 sex 值為 1, 對(duì)應(yīng) java 程序中的枚舉為 Sex.BOY。如果要進(jìn)行值的比較,對(duì)于 java 開發(fā),枚舉最方便的比較方式就是使用 == 直接進(jìn)行比較,這時(shí)就要將 sex 轉(zhuǎn)為枚舉,然后再進(jìn)行后序判斷。

而大多數(shù)程序中的枚舉不是一個(gè)兩個(gè),可能上百上千個(gè)。用到以上處理邏輯的代碼會(huì)更多。那么是否可以將這一邏輯拆分到業(yè)務(wù)代碼之外,但又不改變接口調(diào)用方傳值,也不改變?cè)许憫?yīng)值的形式呢?

另一種情況與前面提到的情況類似。大多數(shù) web 程序中的數(shù)據(jù)最終會(huì)寫入數(shù)據(jù)庫,有些 DB 設(shè)計(jì)對(duì)存儲(chǔ)要求很嚴(yán)格,一些標(biāo)識(shí)類字段希望占用空間越少越好。在上面的情況中 Sex.BOY, 在數(shù)據(jù)庫中希望記錄為 1 而不是 BOY。如果使用 mybatis ,這可能需要實(shí)現(xiàn)大量的 TypeHandler 接口進(jìn)行數(shù)據(jù)轉(zhuǎn)換與逆向轉(zhuǎn)換。是否有辦法自動(dòng)這一過程呢?

下面分別解決上面兩個(gè)需求。這兩個(gè)需求的實(shí)現(xiàn)都依賴類似下面這樣的一個(gè)接口。

public interface CodeData {
    String getCode();
}

接口這樣設(shè)計(jì)是因?yàn)橐陨闲枨蟪橄笠院?,都是將一個(gè)編碼在合適的時(shí)機(jī)轉(zhuǎn)化成一個(gè)枚舉或者將一個(gè)枚舉轉(zhuǎn)為編碼。所以問題的關(guān)鍵只是一個(gè)編碼,至于編碼對(duì)應(yīng)的內(nèi)容,是給人看的,而不是為了計(jì)算機(jī)。

假設(shè) Sex 枚舉如下定義

public enum Sex implements CodeData{
    BOY("1","男"),
    GIRL("2", "女");
    public final String code;
    public final String text;
    public(String code, String text){
        this.code = code;
        this.text = text;
    }
    @Override
    public String getCode(){
        return this.code;
    }
    public String getText(){
        return this.text;
    }
}

為方便說明其使用方式,再簡單定義一個(gè) User 類型

public Class User{
    private String id;
    private String name;
    private Sex sex;
    // 又臭又長的getter/setter , 一定要有
}

對(duì)接口的請(qǐng)求與響應(yīng)數(shù)據(jù)進(jìn)行進(jìn)行自動(dòng)轉(zhuǎn)換

通常我們寫一個(gè) @RestController 接口, 方法定義的參數(shù)大概分為以下兩種情況:

  • 使用 @RequestBody 修飾參數(shù)
  • 不使用 @RequestBody 修飾

當(dāng)然還有其他的情況,暫不涉及。

使用 @RequestBody 的情況

當(dāng)使用 @RequestBody 修飾時(shí),方法定義大概如下:

public User create(@RequestBody User user){
    // ....
    return user;
}

如果你使用了 springboot 那更方便了,默認(rèn)情況下(沒有對(duì) pom 進(jìn)行大的改動(dòng),排除 jackson 依賴),springboot 向工程中注入了 Jackson2ObjectMapperBuilderCustomizer ^2 用于方便用戶修改對(duì) json 類型的請(qǐng)求體進(jìn)行反序列化。我們要做的就是告訴 spring 如何對(duì) Sex 這類的屬性進(jìn)行反序列化。

這里需要用到的知識(shí)點(diǎn)是 jackson 的自定義序列化與反序列化 ^3。

需要針對(duì) Sex 實(shí)現(xiàn) JsonDeserializer

public class SexDeserializer extends JsonDeserializer<Sex> {
    @Override
    public Sex deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
        String text = jsonParser.getText();
        if (StringUtils.isBlank(text)) {
            return null;
        }
        for (Sex e : Sex.values()) {
            if (text.equals(e.getCode())) {
                return e;
            }
        }
        return null;
    }
}

然后將這個(gè) Deserializer 注入到 spring 環(huán)境中的 ObjectMapper 中

@Configuration
public class CustomJsonSerializerConfig{
    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer(CustomJsonSerializerProvider provider) {
        return jacksonObjectMapperBuilder -> {
            jacksonObjectMapperBuilder.deserializerByType(Sex.class, new SexDeserializer());
        }
    }
}

這樣對(duì) Sex 類型的屬性反序列化都會(huì)由 SexDeserializer

下一個(gè)問題是,工程中有大量的枚舉需要做類似的處理,有沒有辦法自動(dòng)注冊(cè)這些類似的 Deserializer? 答案是肯定的, 編程不解決類似的,重復(fù)性的工作,就不要編程了。

要完成自動(dòng)向環(huán)境中注冊(cè), 需要以下兩個(gè)前提

  • 掃描到 classpath 下所有需要自定義反序列化的類型
  • 對(duì)每個(gè)類型構(gòu)造一個(gè) Deserializer

第一個(gè)問題特別復(fù)雜,需要篇幅很長,不能在本文仔細(xì)說明, 涉及到自定義 ClassPathBeanDefinitionScanner, 需要理解 BeanDefinitionRegistryPostProcessorBeanDefinition 以及 springboot 啟動(dòng)的大致步驟??梢詤⒖?mybatis 如何自動(dòng)構(gòu)造 mapper:org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration,org.mybatis.spring.mapper.ClassPathMapperScanner。

掃描類時(shí),我們需要收集所有在 classpath 下的 CodeData 的枚舉類型的實(shí)現(xiàn)類, 將這些枚舉類型的集合注入到 spring 環(huán)境中。我們構(gòu)造下面這樣的一個(gè)類完成這件事。

public record CodeDataTypeProvider<T extends CodeData>(Set<T> codeDataTypes){}

解決第一個(gè)問題后,下一個(gè)問題就是為每一個(gè) CodeData 類型構(gòu)造一個(gè) Deserializer 對(duì)象,我們需要設(shè)計(jì)一個(gè)抽象程序更高的類,這樣就不用為第一個(gè) CodeData 實(shí)現(xiàn)類創(chuàng)建一個(gè)特別的 Deserializer 了:

public class CodeDataDeserializer<T extends CodeData> extends JsonDeserializer<T> {
    private final T[] enums;
    public CodeDataDeserializer(Class<T> enumClass) {
        if (enumClass == null) {
            throw new IllegalArgumentException("Type argument cannot be null");
        }
        if (!enumClass.isEnum()) {
            throw new IllegalArgumentException(enumClass.getName() + "is not a enum");
        }
        this.enums = enumClass.getEnumConstants();
    }
    @Override
    public T deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
        String text = jsonParser.getText();
        if (StringUtils.isBlank(text)) {
            return null;
        }
        for (T e : enums) {
            if (text.equals(e.getCode())) {
                return e;
            }
        }
        return null;
    }
}

最后一步就是將這些 Deserializer 注冊(cè)到環(huán)境中的 ObjectMapper

@Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer(CustomJsonSerializerProvider provider) {
        return jacksonObjectMapperBuilder -> {
            Set<Class<CodeData>> codeDataClass = provider.codeDataType();
            if (CollectionUtils.isEmpty(codeDataClass)) {
                logger.warn("There is no JsonSerializer and CodeData type.");
                return;
            }
            if (codeDataClass != null) {
                for (Class<? extends CodeData> c : codeDataClass) {
                    logger.debug("register custom json serializer {} for {}", "CodeDataSerializer", c);
                    jacksonObjectMapperBuilder.deserializerByType(c, new CodeDataDeserializer<>(c));
                    // 這一步是為了響應(yīng)數(shù)據(jù)時(shí),也能自動(dòng)將CodeData按同樣的規(guī)則序列化
                    jacksonObjectMapperBuilder.serializerByType(c, new CodeDataSerializer<>());
                }
            }
        };
    }

不使用 @RequestBody 的情況

這種情況下,方法的定義通常是下面的樣子

@GetMapping("/getUserBySex")
public SearchUserResultBo getUserBySex(Sex sex){}
@GetMapping("/getUserBySex")
public SearchUserResultBo getUserBySex(@RequestParam Sex sex){}
@GetMapping("/getUserBySex/{sex}")
public SearchUserResultBo getUserBySex(@PathVariable Sex sex){}
@GetMapping("/getUser")
public User getUser(User user){}

這種情況情況下 spring 為我們預(yù)留了 org.springframework.format.Formatter 這個(gè)接口, 它的用法與 jackson 稱之為序列化類型的用法類似, 為需要自定義轉(zhuǎn)換的類型構(gòu)造一個(gè) Formatter , 注冊(cè)到工程環(huán)境中。

同樣存在前面的問題, Formatter 數(shù)量多且邏輯重復(fù)。 但是有前面基礎(chǔ),這里就比較輕松了, 共通的 Formatter 如下:

public class CodeDataFormatter<T extends CodeData> implements Formatter<T> {
    private final Class<T> tClass;
    public CodeDataFormatter(Class<T> tClass) {
        if (tClass == null) {
            throw new IllegalArgumentException("class can not be null.");
        }
        if (!tClass.isEnum()) {
            throw new IllegalArgumentException("class can not be null and must be a enum :" + tClass.getName());
        }
        this.tClass = tClass;
    }
    @Override
    public T parse(@Nonnull String text, @Nonnull Locale locale) throws ParseException {
        if (StringUtils.isBlank(text)) {
            return null;
        }
        T[] values = tClass.getEnumConstants();
        for (T t : values) {
            if (StringUtils.equal(t.getCode(), text)) {
                return t;
            }
        }
        return null;
    }
    @Override
    public String print(T object, Locale locale) {
        if (object == null) {
            return null;
        }
        return object.getCode();
    }
}

注冊(cè)到環(huán)境中的方式如下

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @SuppressWarnings("unchecked")
    @Override
    public void addFormatters(FormatterRegistry registry) {
        Set<Class<CodeData>> codeDataClass = provider.codeDataType();
        if (CollectionUtils.isEmpty(codeDataClass)) {
            logger.warn("There is no CodeData type.");
            return;
        }
        for (Class<? extends CodeData> c : codeDataClass) {
            logger.debug("register formatter {} for {}", "CodeDataFormatter", c);
            registry.addFormatterForFieldType(c, new CodeDataFormatter<>(c));
        }
    }
}

通過 mybatis 將數(shù)據(jù)落庫與讀取

對(duì)于 mybatis 的處理與 @RequestBody 的處理方式類似, 使用 spring boot 的工程都會(huì)通過 mybatis-spring-boot-starter 引入 mybatis , 這個(gè)依賴引入后,程序啟動(dòng)時(shí),mybatis autoconfig 會(huì)獲取環(huán)境中的 ConfigurationCustomizer 對(duì) mybatis 配置進(jìn)行定制化, 所以我們環(huán)境中注入一個(gè) ConfigurationCustomizer 就可以了

   @Bean
    ConfigurationCustomizer mybatisConfigurationCustomizer(MybatisTypeHandlerProvider provider) {
        return configuration -> {
            // customize ...
            Set<Class<CodeData>> codeDataClass = provider.codeDataClass();
            if (CollectionUtils.isEmpty(codeDataClass)) {
                logger.warn("There is no TypeHandler and CodeData type.");
                return;
            }
            TypeHandlerRegistry registry = configuration.getTypeHandlerRegistry();
            if (codeDataClass != null) {
                for (Class<CodeData> c : codeDataClass) {
                    logger.debug("register type handler {} for {}", "CodeDataTypeHandler", c);
                    registry.register(c, new CodeDataTypeHandler<>(c));
                }
            }
        };
    }

以上就是spring mybatis環(huán)境常量與枚舉轉(zhuǎn)換示例詳解的詳細(xì)內(nèi)容,更多關(guān)于spring mybatis環(huán)境常量枚舉轉(zhuǎn)換的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • SpringBoot攔截器執(zhí)行后,Controller層不執(zhí)行的問題及解決

    SpringBoot攔截器執(zhí)行后,Controller層不執(zhí)行的問題及解決

    請(qǐng)求攔截器中若調(diào)用getInputStream()讀取流,會(huì)導(dǎo)致Controller層無法獲取請(qǐng)求體,引發(fā)400錯(cuò)誤,解決方案:避免在攔截器中讀取流,或使用緩存(如將流轉(zhuǎn)為字符串)確保數(shù)據(jù)可重復(fù)使用
    2025-07-07
  • 一篇文章帶你詳解Spring的AOP

    一篇文章帶你詳解Spring的AOP

    這篇文章主要為大家介紹了Spring的AOP,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-01-01
  • 解析java稀疏數(shù)組如何幫助我們節(jié)省內(nèi)存提升性能

    解析java稀疏數(shù)組如何幫助我們節(jié)省內(nèi)存提升性能

    這篇文章主要為大家介紹了java稀疏數(shù)組如何幫助我們節(jié)省內(nèi)存提升性能解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-11-11
  • java中最易犯錯(cuò)的特殊字符示例詳解

    java中最易犯錯(cuò)的特殊字符示例詳解

    這篇文章主要給大家介紹了關(guān)于java中最易犯錯(cuò)的特殊字符的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用java具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09
  • Java上傳視頻實(shí)例代碼

    Java上傳視頻實(shí)例代碼

    本文通過實(shí)例代碼給大家講解了java上傳視頻功能,代碼分為頁面前臺(tái)和后臺(tái),工具類,具體實(shí)例代碼大家通過本文學(xué)習(xí)吧
    2018-01-01
  • 全面解讀Java編程中的內(nèi)部類

    全面解讀Java編程中的內(nèi)部類

    這篇文章主要介紹了Java的內(nèi)部類,包括類成員訪問等Java入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下
    2015-09-09
  • Netty分布式客戶端接入流程初始化源碼分析

    Netty分布式客戶端接入流程初始化源碼分析

    這篇文章主要介紹了Netty分布式客戶端接入流程初始化源碼分析,有關(guān)channelConfig有關(guān)的初始化過程剖析,有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2022-03-03
  • 深入理解LinkedHashSet集合的底層機(jī)制

    深入理解LinkedHashSet集合的底層機(jī)制

    這篇文章主要介紹了LinkedHashSet的底層機(jī)制詳解,LinkedHashSet根據(jù)元素的hashCode值來決定元素的存儲(chǔ)位置,同時(shí)使用鏈表維護(hù)元素的次序,?這使得元素看起來是以插入順序保存的,需要的朋友可以參考下
    2023-10-10
  • Java中實(shí)現(xiàn)高清圖片壓縮的兩種方案(最新推薦)

    Java中實(shí)現(xiàn)高清圖片壓縮的兩種方案(最新推薦)

    文章首先介紹了Java中進(jìn)行高清圖片壓縮的基本方法,包括使用Java標(biāo)準(zhǔn)庫ImageIO和第三方庫ApacheCommonsCompress,通過示例代碼展示了如何調(diào)整圖像質(zhì)量和使用第三方工具來壓縮圖片文件,感興趣的朋友跟隨小編一起看看吧
    2025-01-01
  • Java String類的性質(zhì)與比較

    Java String類的性質(zhì)與比較

    字符串廣泛應(yīng)用 在 Java 編程中,在 Java 中字符串屬于對(duì)象,Java 提供了 String 類來創(chuàng)建和操作字符串,本文將為你帶來詳細(xì)介紹,感興趣的朋友繼續(xù)往下看吧
    2021-10-10

最新評(píng)論

临沧市| 玉山县| 峡江县| 松滋市| 如东县| 镇康县| 平潭县| 玛曲县| 淮南市| 嘉黎县| 嫩江县| 德庆县| 宣化县| 武冈市| 霍山县| 高要市| 玛纳斯县| 怀宁县| 邛崃市| 金平| 淮安市| 阳东县| 曲水县| 新源县| 滦平县| 思南县| 景东| 广丰县| 钟祥市| 涟源市| 千阳县| 和硕县| 长岭县| 灵石县| 锡林浩特市| 尉犁县| 亚东县| 德江县| 汉寿县| 云梦县| 穆棱市|