SpringBoot+MyBatis Plus对Map中Date格式转换的处理
在 SpringBoot 项目中, 如何统一 JSON 格式化中的日期格式
问题
现在的关系型数据库例如PostgreSQL/MySQL, 都已经对 JSON 类型提供相当丰富的功能, 项目中对于不需要检索但是又需要结构化的存储, 会在数据库中产生很多 JSON 类型的字段, 与 Jackson 做对象的序列化和反序列化配合非常方便.
如果 JSON 都是类定义的, 这个序列化和反序列化就非常透明 -- 不需要任何干预, 写进去是什么, 读出来就是什么. 但是如果 JSON 在 Java 代码中是定义为一个 Map, 例如 Map<String, Object> 那么就有问题了, 对于 Date 类型的数据, 在存入之前是 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已经变成了 Long 类型
Object k1 = dummy.getProperties().get("k1");
原因
不管是使用原生的 MyBatis 还是包装后的 MyBatis Plus, 在对 JSON 类型字段进行序列化和反序列化时, 都需要借助类型判断, 调用对应的处理逻辑, 大部分情况, 使用的是默认的 Jackson 的 ObjectMapper, 而 ObjectMapper 对 Date 类型默认的序列化方式就是取时间戳, 对于早于1970年之前的日期, 生成的是一个负的长整数, 对于1970年之后的日期, 生成的是一个正的长整数.
查看 ObjectMapper 的源码, 可以看到其对Date格式的序列化和反序列化方式设置于_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();
}
}
默认的序列化反序列化选项, 使用了一个常量 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);
}
实际的转换工作在 SerializerProvider 类中, 转换方法为
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;
}
另外还可以自定义序列化反序列化方法, 实现 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() 设置日期格式, 改变默认的日期序列化反序列化行为. 这种方式只对调用此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 一般是当作线程安全使用的, 而 SimpleDateFormat 并非线程安全, 在这里使用是否会有问题? 关于这个疑虑, 可以查看 这个链接
@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 中创建的 Jackson ObjectMapper有效, 例如 HTTP 请求和响应中对 Date 类型的转换
spring:
...
jackson:
date-format: yyyy-MM-dd HH:mm:ss
整体方案
国内项目, 几乎都会希望落库时日期就是日期的样子(方便看数据库表), 所谓日期的样子就是yyyy-MM-dd HH:mm:ss
格式的字符串. 如果怕麻烦, 就通通都用这个格式了.
这样统一存在的隐患是丢失毫秒部分. 这个问题业务人员基本上是不会关心的. 如果需要, 就在格式中加上.
第一是 Spring 配置, 这样所有的请求响应都统一了
spring:
...
jackson:
date-format: yyyy-MM-dd HH:mm:ss
第二是定义一个工具类, 把 ObjectMapper 自定义一下, 这样所有手工转换的地方也统一了, 注意留一个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 的设置, 即下面的 changeObjectMapper() 这个方法, 直接换成了上面工具类中定义的 ObjectMapper, 这样在 MyBatis 读写数据库时的格式也统一了.
@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());
}
}
参考
SpringBoot+MyBatis Plus对Map中Date格式转换的处理的更多相关文章
- Struts2中Date日期转换的问题
今天跑程序的时候莫名其妙的出现了下面的一个异常: java.lang.NoSuchMethodException:com.ca.agent.model.mybatis.ApprovalInforC ...
- JAVA中时间格式转换
1.将任意日期格式的字符串转换为指定格式的字符串 //默认格式 String s1 = "20190110133236"; //给定格式 String s2 = "201 ...
- layui的table中日期格式转换
我使用的layui的2.4.5版本,在util中已经带了格式转换的函数.这里主要记录如何使用. 未使用前在table中显示如下: 前端代码如下: {field:'createDate', title: ...
- Sql与C#中日期格式转换总结
SQL中的转换方法: 一.将string转换为datetime,主要是使用Convert方法, 方法,Convert(datetime [ ( length ) ] , expression, [st ...
- js读取excel中日期格式转换问题
在使用js-xlsx插件来读取excel时,会将2018/10/16这种数据自动装换成48264.12584511. 所以需要自己手动再转换回来 // excel读取2018/01/01这种时间格式是 ...
- Mac中的格式转换如何用读写工具Tuxera NTFS完成
Tuxera NTFS for Mac是一款专门为Mac用户提供的NTFS驱动软件,它不仅可以进行磁盘文件的访问.编辑.传输和存储,还可以对硬盘进行维修检查以及修复. 今天小编就给大家简单介绍一下Tu ...
- SAP BDC 调用中 金额格式转换
在BDC调用中,由于用户设置不同,导致金额.日期等字段的输入格式不正确.此处给出 自创 金额转换FM 并配有 调用方式. function zgm_conver_cuur. *"------ ...
- Sql Server中Float格式转换字符串varchar方法(转)
1.[Sql Server](70) SELECT CONVERT(varchar(100), CAST(@testFloat AS decimal(38,2)))SELECT STR(@testF ...
- linq中日期格式转换或者比较,程序报错说不支持方法的解决办法
public void TestMethod1(){using (var _context = new hotelEntities()){var rq = DateTime.Now.Date;var ...
随机推荐
- SQL基本概念和SQL通用语法
SQL 1.什么是SQL? Structured Query Language:结构化查询语句 其实就是定义了操作所有关系型数据库的规则.每一种数据库操作的方式存在不一样的地方称为"方言&q ...
- DP の 百题大过关(5/100)
动态规划自古以来是DALAO凌虐萌新的分水岭,但有些OIer认为并没有这么重要--会打暴力,大不了记忆化.但是其实,动态规划学得好不好,可以彰显出一个OIerOIer的基本素养--能否富有逻辑地思 ...
- AlexNet—论文分析及复现
AlexNet卷积神经网络是由Alex Krizhevsky等人在2012年的ImagNet图像识别大赛获得冠军的一个卷积神经网络,该网络放到现在相对简单,但也是深度学习不错的卷积神经网络.论文:&l ...
- mybatis 02: 添加并简单使用mybatis
三层架构 项目开发时,遵循的一种设计模式,分为三层 界面层:用来接收客户端输入的数据,调用业务逻辑层进行功能处理,返回结果给客户端 过去的servlet就完成了界面层的功能(但是他做的更多) 业务逻辑 ...
- 笃情开源:我和 Apache DolphinScheduler 社区的故事
背景 本文的主人翁是 2 次飞机参会现场交流,四天研究就把 DolphinScheduler 用上生产的来自车联网行业的大数据 boy - 黄立同学.怎么样,听起来是不是有点 crazy?下面就来看看 ...
- 【常见】CSS3进度条Loading动画
现在,GIF 格式的进度条已经越来越少,CSS 进度条如雨后春笋般涌现.CSS3的崛起,更使得动态效果得以轻松实现,未来,必定是CSS3的天下,所以今天我就来分享一下几个常见的CSS3进度条Loadi ...
- mustache.js常见用法
一.mustache基于JS模板引擎,能较为快捷和简单得实现数据得渲染 用法: 1.CDN引入mustache.js,以下是4.0.1版本,有需要可以去github上查询其他版本的代码. (funct ...
- Spring源码环境搭建
Spring源码在github上,地址是https://github.com/spring-projects/spring-framework/,选择5.3.x版本,直接从github上克隆项目网速很 ...
- 一些有用的数学知识(Updating)
文章目录 拉格朗日插值公式 微分中值定理 费马引理 拉格朗日中值定理 柯西中值定理 洛必达法则 连分数(NOI2021 D2T2 考点) 定义 结论 定理1 定理2 定理3 定理4 定理5 欧拉公式 ...
- 字符串KMP——用途广泛的字符串匹配算法 + 扩展KMP——特殊定义的字符串匹配
引 入 引入 引入 " SY 和 WYX 在看毛片.(几 毛 钱买到的动作 片,毛 片) WYX 突然想回味一个片段,但是只记得台词里面有一句挺长的 " ∗ ∗ ∗ ∗ **** ...