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 商城系統,功能超全,超漂亮!