1. 程式人生 > >SpringBoot專案,如何優雅的把介面引數中的空白值替換為null值?

SpringBoot專案,如何優雅的把介面引數中的空白值替換為null值?

問題發生

我們公司程式碼生成的時候,查詢列表統一都是使用了setEntity() ,查詢寫法如下:

public List<BasReservoirArea> selectList(BasReservoirArea basReservoirArea) {
    QueryWrapper<BasReservoirArea> where = new QueryWrapper<>();
    where.setEntity(basReservoirArea);
    return baseMapper.selectList(where);
}

 

查詢的方法是Get方法:

前端是通過url加引數傳過來的,如果有一個引數值為空的時候,由於setEntity() 並不過濾空白,執行sql的時候 會把""作為引數去當做查詢條件,查詢就出現了問題:

於是我就想把空白轉換為null來解決這個問題了。

初始解決

一開始自然而然想到在setEntity之前先判斷, 如果BasReservoirArea這個例項有欄位的值是空白就設定為null

//1.物件轉map
Map<Object, Object> map = MapUtil.beanToMap(test);
//2.移除空值
MapUtil.removeNullValue(map);
//3.map轉回物件
Test entity = JSON.parseObject(JSON.toJSONString(map), Test.class);

 

用到的工具類如下

/**
* 將物件屬性轉化為map結合
*/
public static <T> Map<Object, Object> beanToMap(T bean) {
 Map<Object, Object> map = new HashMap<>();
 if (bean != null) {
  BeanMap beanMap = BeanMap.create(bean);
  for (Object key : beanMap.keySet()) {
   map.put(key, beanMap.get(key));
  }
 }
 return map;
}
/**
* 移除map中的value空值
*
* @param map
* @return
*/
public static void removeNullValue(Map map) {
 Set set = map.keySet();
 for (Iterator iterator = set.iterator(); iterator.hasNext(); ) {
  Object obj = (Object) iterator.next();
  Object value = (Object) map.get(obj);
  remove(value, iterator);
 }
}

 

問題解決了。

優化

由於感覺上面的解決方案不夠專業,不夠優雅,所以先尋找更好的解決辦法,在後端接收引數值的時候,如果接收的是空白,直接設定為null, 這樣就不需要再次轉換了。

解決問題首先要考慮兩種情況,一種是前端通過Get請求,路徑上帶引數;另一種是Post請求,帶著Request報文。

Post請求報文體

由於筆者熟悉Post中報文體的轉換,知道是MappingJackson2HttpMessageConverter結合Jackson實現報文體轉換為例項的,而且也研究過Jackson, 所以解決辦法如下

建立一個針對於String.class的Jackson的反序列類:

public class StringDescrializer extends JsonDeserializer<String> {
    @Override
    public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
        String value = jsonParser.getValueAsString();
        if (value == null || "".equals(value.trim())) {
            return null;
        }
        return value;
    }
}

 

建立一個MappingJackson2HttpMessageConverter  Bean:

@Bean
@Primary
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
 MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
 //設定解析JSON工具類
 ObjectMapper objectMapper = new ObjectMapper();
 objectMapper.getSerializerProvider().setNullValueSerializer(
  new JsonSerializer<Object>() {
   @Override
   public void serialize(Object value, JsonGenerator jsonGenerator, SerializerProvider        serializerProvider) throws IOException {
     jsonGenerator.writeString("");
   }
  }
 );
 
 SimpleModule simpleModule = new SimpleModule();
 simpleModule.addDeserializer(String.class, new StringDescrializer());
    //註冊自定義的StringDescrializer
    //registerModules函式可以註冊多個Module
 objectMapper.registerModule(simpleModule);

    //忽略未知屬性 防止解析報錯
    objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

    jsonConverter.setObjectMapper(objectMapper);
    List<MediaType> list = new ArrayList<>();
    list.add(MediaType.APPLICATION_JSON_UTF8);
    jsonConverter.setSupportedMediaTypes(list);
    return jsonConverter;
}

 

對於Post報文體來說,測試成功了。

Get路徑帶引數

上面的解決方法不適用於Get方法路徑帶引數的情況,所以需要另外想辦法了。

由於我使用過@InitBinder註解,知道可以注入自定義的PropertyEditor, 在Editor裡面可以自定義格式或者返回值,於是,自定義一個StringEditor來處理空白的問題:、

public class StringEditor extends PropertyEditorSupport {
    //setAsText完成字串到具體物件型別的轉換,
    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        if (text == null || "".equals(text.trim())) {
            text = null;
        }
        setValue(text);
    }

    //getAsText完成具體物件型別到字串的轉換。
    @Override
    public String getAsText() {
        if (getValue() != null) {
            return getValue().toString();
        }
        return null;
    }
}

 

想要全域性controller共享這個Databinder:

@ControllerAdvice
public class GlobalControllerAdiviceController {
    //WebDataBinder是用來繫結請求引數到指定的屬性編輯器,可以繼承WebBindingInitializer
    //來實現一個全部controller共享的dataBiner 
    @InitBinder
    public void dataBind(WebDataBinder binder) {
        ///給指定型別註冊型別轉換器操作
        binder.registerCustomEditor(String.class, new StringEditor());
    }
}

 

對於Get路徑帶引數來說,測試也成功了

思考

解決完問題後,還是覺得不夠優雅,覺得spring 應該會考慮到這種情況,終於在spring 的文件中查閱到StringTrimmerEditor(https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-beans) 可以實現「Get」方法時引數去除空格:

只不過這個editor預設沒有註冊,需要手工註冊。

@ControllerAdvice
public class GlobalControllerAdiviceController {
    //WebDataBinder是用來繫結請求引數到指定的屬性編輯器,可以繼承WebBindingInitializer
    //來實現一個全部controller共享的dataBiner Java程式碼
    @InitBinder
    public void dataBind(WebDataBinder binder) {
        ///註冊
        binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
    }
}

 

注意,StringTrimmerEditor構造方法中有一個引數,如果傳入true,則會將空白轉換為null. 這樣前面寫的StringEditor就不用了,spring 已經幫我們寫好了。

對於「Post」報文體來說,實際上我只需要改變的是「Jackson ObjectMapper」,不需要自定義整個MappingJackson2HttpMessageConverter  ,只需要自定義Jackson ObjectMapper.百度了一下,果然有同學已經有了解決方案:

@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
    return new Jackson2ObjectMapperBuilderCustomizer() {
        @Override
        public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
            jacksonObjectMapperBuilder
                    .deserializerByType(String.class, new StdScalarDeserializer<String>(String.class) {
                        @Override
                        public String deserialize(JsonParser jsonParser, DeserializationContext ctx)
                                throws IOException {
                            // 重點在這兒:如果為空白則返回null
                            String value = jsonParser.getValueAsString();
                            if (value == null || "".equals(value.trim())) {
                                return null;
                            }
                            return value;
                        }
                    });
        }
    };
}

 

把上面的自定義StringDescrializer和MappingJackson2HttpMessageConverter去掉, 只保留上面的就行。

後記

好多問題,其實spring 都已經提供瞭解決方案,但是spring體系目前太龐大了,所以好多API和功能都不為人知。所以碰上問題就記錄下來是個很好的習慣

推薦好文

強大,10k+點讚的 SpringBoot 後臺管理系統竟然出了詳細教程!

分享一套基於SpringBoot和Vue的企業級中後臺開源專案,程式碼很規範!

能掙錢的,開源 SpringBoot 商城系統,功能超全,超漂亮!