SpringBoot+MyBatisPlus對Map中Date格式轉(zhuǎn)換處理的方法詳解
在 SpringBoot 項目中, 如何統(tǒng)一 JSON 格式化中的日期格式
問題
現(xiàn)在的關(guān)系型數(shù)據(jù)庫例如PostgreSQL/MySQL, 都已經(jīng)對 JSON 類型提供相當(dāng)豐富的功能, 項目中對于不需要檢索但是又需要結(jié)構(gòu)化的存儲, 會在數(shù)據(jù)庫中產(chǎn)生很多 JSON 類型的字段, 與 Jackson 做對象的序列化和反序列化配合非常方便.
如果 JSON 都是類定義的, 這個序列化和反序列化就非常透明 -- 不需要任何干預(yù), 寫進去是什么, 讀出來就是什么. 但是如果 JSON 在 Java 代碼中是定義為一個 Map, 例如 Map<String, Object> 那么就有問題了, 對于 Date 類型的數(shù)據(jù), 在存入之前是 Date, 取出來之后就變成 Long 了.
SomePO po = new SomePO(); //... Map<String, Object> map = new HashMap<>(); map.put("k1", new Date()); po.setProperties(map); //... mapper.insert(po); //... SomePO dummy = mapper.select(po.id); // 這里的k1已經(jīng)變成了 Long 類型 Object k1 = dummy.getProperties().get("k1");
原因
不管是使用原生的 MyBatis 還是包裝后的 MyBatis Plus, 在對 JSON 類型字段進行序列化和反序列化時, 都需要借助類型判斷, 調(diào)用對應(yīng)的處理邏輯, 大部分情況, 使用的是默認(rèn)的 Jackson 的 ObjectMapper, 而 ObjectMapper 對 Date 類型默認(rèn)的序列化方式就是取時間戳, 對于早于1970年之前的日期, 生成的是一個負(fù)的長整數(shù), 對于1970年之后的日期, 生成的是一個正的長整數(shù).
查看 ObjectMapper 的源碼, 可以看到其對Date格式的序列化和反序列化方式設(shè)置于_serializationConfig 和 _deserializationConfig 這兩個成員變量中, 可以通過 setDateFormat() 進行修改
public class ObjectMapper extends ObjectCodec implements Versioned, Serializable { //... protected SerializationConfig _serializationConfig; protected DeserializationConfig _deserializationConfig; //... public ObjectMapper setDateFormat(DateFormat dateFormat) { this._deserializationConfig = (DeserializationConfig)this._deserializationConfig.with(dateFormat); this._serializationConfig = this._serializationConfig.with(dateFormat); return this; } public DateFormat getDateFormat() { return this._serializationConfig.getDateFormat(); } }
默認(rèn)的序列化反序列化選項, 使用了一個常量 WRITE_DATES_AS_TIMESTAMPS, 在類 SerializationConfig 中進行判斷, 未指定時使用的是時間戳
public SerializationConfig with(DateFormat df) { SerializationConfig cfg = (SerializationConfig)super.with(df); return df == null ? cfg.with(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) : cfg.without(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); }
實際的轉(zhuǎn)換工作在 SerializerProvider 類中, 轉(zhuǎn)換方法為
public final void defaultSerializeDateValue(long timestamp, JsonGenerator gen) throws IOException { if (this.isEnabled(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)) { gen.writeNumber(timestamp); } else { gen.writeString(this._dateFormat().format(new Date(timestamp))); } } public final void defaultSerializeDateValue(Date date, JsonGenerator gen) throws IOException { if (this.isEnabled(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)) { gen.writeNumber(date.getTime()); } else { gen.writeString(this._dateFormat().format(date)); } }
解決
局部方案
1. 字段注解
這種方式可以用在固定的類成員變量上, 不改變整體行為
public class Event { public String name; @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss") public Date eventDate; }
另外還可以自定義序列化反序列化方法, 實現(xiàn) StdSerializer
public class CustomDateSerializer extends StdSerializer<Date> { //... }
就可以在 @JsonSerialize 注解中使用
public class Event { public String name; @JsonSerialize(using = CustomDateSerializer.class) public Date eventDate; }
2. 修改 ObjectMapper
通過 ObjectMapper.setDateFormat() 設(shè)置日期格式, 改變默認(rèn)的日期序列化反序列化行為. 這種方式只對調(diào)用此ObjectMapper的場景有效
private static ObjectMapper createObjectMapper() { ObjectMapper objectMapper = new ObjectMapper(); SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); objectMapper.setDateFormat(df); return objectMapper; }
因為 ObjectMapper 一般是當(dāng)作線程安全使用的, 而 SimpleDateFormat 并非線程安全, 在這里使用是否會有問題? 關(guān)于這個疑慮, 可以查看 這個鏈接
@StaxMan: I am a bit concerned if ObjectMapper is still thread-safe after ObjectMapper#setDateFormat() is called. It is known that SimpleDateFormat is not thread safe, thus ObjectMapper won't be unless it clones e.g. SerializationConfig before each writeValue() (I doubt). Could you debunk my fear? – dma_k Aug 2, 2013 at 12:09
DateFormat is indeed cloned under the hood. Good suspicion there, but you are covered. ?? – StaxMan Aug 2, 2013 at 19:43
3. 修改 SpringBoot 配置
增加配置 spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
, 這種配置, 只對 Spring BeanFactory 中創(chuàng)建的 Jackson ObjectMapper有效, 例如 HTTP 請求和響應(yīng)中對 Date 類型的轉(zhuǎn)換
spring: ... jackson: date-format: yyyy-MM-dd HH:mm:ss
整體方案
國內(nèi)項目, 幾乎都會希望落庫時日期就是日期的樣子(方便看數(shù)據(jù)庫表), 所謂日期的樣子就是yyyy-MM-dd HH:mm:ss
格式的字符串. 如果怕麻煩, 就通通都用這個格式了.
這樣統(tǒng)一存在的隱患是丟失毫秒部分. 這個問題業(yè)務(wù)人員基本上是不會關(guān)心的. 如果需要, 就在格式中加上.
第一是 Spring 配置, 這樣所有的請求響應(yīng)都統(tǒng)一了
spring: ... jackson: date-format: yyyy-MM-dd HH:mm:ss
第二是定義一個工具類, 把 ObjectMapper 自定義一下, 這樣所有手工轉(zhuǎn)換的地方也統(tǒng)一了, 注意留一個getObjectMapper()方法
public class JacksonUtil { private static final Logger log = LoggerFactory.getLogger(JacksonUtil.class); private static final ObjectMapper MAPPER = createObjectMapper(); private JacksonUtil() {} private static ObjectMapper createObjectMapper() { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false); objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); objectMapper.setDateFormat(df); return objectMapper; } public static ObjectMapper getObjectMapper() { return MAPPER; } }
第三是啟動后修改 MyBatisPlus 的設(shè)置, 即下面的 changeObjectMapper() 這個方法, 直接換成了上面工具類中定義的 ObjectMapper, 這樣在 MyBatis 讀寫數(shù)據(jù)庫時的格式也統(tǒng)一了.
@Configuration @MapperScan(basePackages = {"com.somewhere.commons.impl.mapper"}) public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.POSTGRE_SQL)); interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; } @PostConstruct public void changeObjectMapper() { // This will unify the date format with util methods JacksonTypeHandler.setObjectMapper(JacksonUtil.getObjectMapper()); } }
到此這篇關(guān)于SpringBoot+MyBatisPlus對Map中Date格式轉(zhuǎn)換處理的方法詳解的文章就介紹到這了,更多相關(guān)SpringBoot MyBatisPlus處理Date格式轉(zhuǎn)換內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
IDEA關(guān)于.properties資源文件的編碼調(diào)整問題
這篇文章主要介紹了IDEA關(guān)于.properties資源文件的編碼調(diào)整問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-06-06SpringBoot入口類和@SpringBootApplication講解
這篇文章主要介紹了SpringBoot入口類和@SpringBootApplication講解,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03完美解決Server?returned?HTTP?response?code:403?for?URL報錯問題
在調(diào)用某個接口的時候,突然就遇到了Server?returned?HTTP?response?code:?403?for?URL報錯這個報錯,導(dǎo)致獲取不到接口的數(shù)據(jù),下面小編給大家分享解決Server?returned?HTTP?response?code:403?for?URL報錯問題,感興趣的朋友一起看看吧2023-03-03詳解Java的Hibernate框架中的Interceptor和Collection
這篇文章主要介紹了Java的Hibernate框架中的Interceptor和Collection,Hibernate是Java的SSH三大web開發(fā)框架之一,需要的朋友可以參考下2016-01-01