news 2026/8/6 17:05:34

记一次 MyBatis 与 JdbcTemplate 日期类型混用导致的 Jackson 格式化“失效”问题

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
记一次 MyBatis 与 JdbcTemplate 日期类型混用导致的 Jackson 格式化“失效”问题

记一次 MyBatis 与 JdbcTemplate 日期类型混用导致的 Jackson 格式化“失效”问题

背景

最近在项目里遇到了一个让人有些头疼的问题:同一个接口返回的 JSON 数据中,日期字段的格式居然不一样。排查之后发现,原因是项目中同时使用了 MyBatis 和 JdbcTemplate 两种方式查询数据库,而两者返回的日期类型不一致,加上 Jackson 在处理不同日期类型时的默认行为不同,最终导致了格式化“部分生效”的现象。我把整个过程记录下来,希望能帮助遇到同样问题的朋友。

现象描述

我们的项目在序列化对象时会统一配置 Jackson 的日期格式,大致如下:

ObjectMapper mapper = new ObjectMapper(); mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
  • 使用MyBatis的 Mapper 接口查询数据,实体类中时间字段用的是java.util.Date。序列化后,日期能够成功按照yyyy-MM-dd HH:mm:ss格式输出。
  • 使用JdbcTemplatequeryForList方法查询数据,结果用Map<String, Object>接收。此时发现,Map 中的日期字段输出格式变成了类似2025-12-17的纯日期字符串,秒和时分部分完全不见了,并没有按照我们设置的yyyy-MM-dd HH:mm:ss来展示。

明明都是同一个ObjectMapper实例,为什么只有java.util.Date类型被正常格式化了,而 JdbcTemplate 返回的日期却“不听话”呢?

原因分析

1. 两种 Date 类型本身就有差异

首先需要明确,java.util.Datejava.sql.Date虽然看着很像,但用途和设计有本质区别。

  • java.util.Date:通用的日期时间类,同时包含日期和时间信息(精确到毫秒),大多数业务实体都使用它。
  • java.sql.Date:继承自java.util.Date,是 JDBC 专门用来映射数据库DATE类型的类。为了符合 SQL 标准的 DATE(只包含年、月、日),它的时间部分会被规整为零(即 00:00:00),并且其toString()方法也只输出yyyy-MM-dd格式。

当使用JdbcTemplate.queryForList时,JDBC 驱动会按照标准映射关系,将数据库的DATE类型字段自动映射为java.sql.Date对象放入 Map 中。而 MyBatis 默认会将日期映射为java.util.Date(我们也可以在实体类中显式使用java.util.Date),这就导致两种方式产出的日期对象类型不完全相同。

2. Jackson 对不同 Date 类型的处理并不一致

Jackson 在序列化时,对不同日期类型使用不同的内置序列化器。关键点在于:

  • java.util.Date类型,Jackson 使用DateSerializer。通过ObjectMapper.setDateFormat()设置的SimpleDateFormat会被该序列化器采纳,所以我们可以很容易地实现全局格式化。
  • java.sql.Date类型,Jackson 则使用专门的SqlDateSerializer。这个序列化器为了准确表达 SQL 日期的含义(即只包含日期部分),会直接忽略全局设置的DateFormat,而是调用java.sql.DatetoString()方法或仅输出日期部分,因此结果总是yyyy-MM-dd这样的格式。

这就是为什么我们明明配置了全局日期格式,java.util.Date可以生效,而java.sql.Date却纹丝不动的原因——根本不是“失效”,而是 Jackson 压根就没打算对java.sql.Date使用我们设置的SimpleDateFormat

解决方案:自定义序列化器并全局注册

既然默认的SqlDateSerializer不买账,我们就只能自己动手,通过自定义序列化器来接管java.sql.Date的格式化逻辑,并将它全局注册到ObjectMapper中。这样无论java.sql.Date出现在实体类、Map 还是任何地方,都能按照我们指定的格式输出。

思路很直接:创建一个继承JsonSerializer<java.sql.Date>的序列化类,在serialize方法中用SimpleDateFormat格式化日期,然后通过SimpleModule注册。

自定义序列化器代码如下:

importcom.fasterxml.jackson.core.JsonGenerator;importcom.fasterxml.jackson.databind.JsonSerializer;importcom.fasterxml.jackson.databind.SerializerProvider;importjava.io.IOException;importjava.sql.Date;importjava.text.SimpleDateFormat;publicclassCustomSqlDateSerializerextendsJsonSerializer<Date>{privatestaticfinalSimpleDateFormatFORMAT=newSimpleDateFormat("yyyy-MM-dd HH:mm:ss");@Overridepublicvoidserialize(Datevalue,JsonGeneratorgen,SerializerProviderserializers)throwsIOException{if(value==null){gen.writeNull();}else{gen.writeString(FORMAT.format(value));}}}

注册到ObjectMapper的方式也很简单:

importcom.fasterxml.jackson.databind.ObjectMapper;importcom.fasterxml.jackson.databind.module.SimpleModule;importjava.sql.Date;ObjectMappermapper=newObjectMapper();// 如果还有针对 java.util.Date 的全局格式,可继续保留// mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));SimpleModulemodule=newSimpleModule();module.addSerializer(Date.class,newCustomSqlDateSerializer());mapper.registerModule(module);

这样一来,所有java.sql.Date类型字段的序列化都会走我们自定义的逻辑,输出yyyy-MM-dd HH:mm:ss格式的字符串,与 MyBatis 实体中java.util.Date的表现达成一致。问题顺利解决。

补充说明:因为JdbcTemplate.queryForList返回的是Map,我们无法像处理实体类那样在字段上加注解,所以全局注册才是真正有效的方案。这也正是题目中所说的“只能自定义序列化类指定 java.sql.Date 类型字段的格式化方式才生效”的含义。

总结

这次问题的根因可以归纳为两点:

  1. JDBC 规范导致类型差异:JdbcTemplate 返回的Map中日期字段默认是java.sql.Date,而 MyBatis 实体常使用java.util.Date
  2. Jackson 对 SQL 日期类型的特殊照顾SqlDateSerializer有自己独立的格式输出逻辑,无视全局DateFormat设置。

通过自定义JsonSerializer并全局注册到ObjectMapper,我们成功让java.sql.Date也听从了统一的日期格式配置。这件事也提醒我,在混用多种数据访问方式时,要格外注意框架的默认行为,小小的类型差异就可能引发令人困惑的序列化问题。

希望这篇记录对你有用。如果你也有类似的踩坑经历,欢迎交流。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/6 16:58:47

电容屏与电阻屏技术解析:原理、选型及触觉反馈设计

1. 从“按下去”到“摸一下”&#xff1a;交互界面的范式转移十几年前&#xff0c;我第一次接触一台全触屏手机时&#xff0c;那种感觉是颠覆性的。光滑的玻璃表面&#xff0c;没有任何物理按键的凸起&#xff0c;所有的操作都通过指尖的轻触、滑动来完成。这与当时主流的、带有…

作者头像 李华
网站建设 2026/8/6 16:57:11

Unity WebGL集成海康摄像头:AVProVideo+代理服务器实战方案

1. 项目概述&#xff1a;当数字孪生遇上实时视频流 最近在做一个工业园区的数字孪生项目&#xff0c;客户的核心需求之一&#xff0c;就是要把园区里几十个海康威视摄像头的实时监控画面&#xff0c;无缝集成到基于Unity WebGL构建的3D孪生场景里。听起来是个很常见的需求&…

作者头像 李华
网站建设 2026/8/6 16:52:47

Unity游戏开发实战:Android 13权限适配与优雅权限管理器实现

1. 项目概述&#xff1a;为什么Android 13权限适配是Unity开发者的“必修课”如果你最近用Unity打包的Android游戏&#xff0c;在Android 13设备上运行时&#xff0c;突然发现相机打不开、相册访问不了&#xff0c;或者通知不弹了&#xff0c;别急着怀疑是自己的代码写错了。这…

作者头像 李华
网站建设 2026/8/6 16:51:18

VSCode集成小米MiMo AI:免费Token获取与CodeGPT插件配置实战

1. 项目缘起&#xff1a;从“白嫖”到生产力工具的探索 最近在折腾AI编程助手的时候&#xff0c;发现了一个挺有意思的事儿&#xff1a;小米的MiMo模型&#xff0c;居然能提供百万级别的免费Token额度。这可不是什么小道消息&#xff0c;而是官方渠道可以获取的资源。对于咱们这…

作者头像 李华
网站建设 2026/8/6 16:50:05

mpv方向键快进快退失灵?关键帧定位与精确搜索配置详解

1. 问题场景&#xff1a;当方向键快进快退“失灵”时作为一名重度视频消费者和折腾爱好者&#xff0c;我几乎在所有设备上都用mpv作为主力播放器。它的轻量、高效和无限的可定制性&#xff0c;让我可以打造出完全符合个人习惯的观影环境。然而&#xff0c;就在最近一次更新配置…

作者头像 李华
网站建设 2026/8/6 16:49:46

Unity VR物体吸附效果实现:从XRI速度追踪到手感调优

1. 项目概述&#xff1a;为什么需要“吸附到手上”&#xff1f;在VR开发里&#xff0c;让一个物体“吸附”到玩家手上&#xff0c;听起来是个简单的需求&#xff0c;但背后藏着交互设计的核心逻辑。新手可能会想&#xff0c;不就是把手的位置赋值给物体吗&#xff1f;但实际做起…

作者头像 李华