Java类加载器与类实例化,这次聊点实在的
从一次线上配置读不到的诡异问题说起,梳理 ExtClassLoader 的边界与系统类加载器读配置的正确姿势,再走一遍 new 一个对象背后从类加载到构造方法执行的完整链路,并用父子类 demo 把初始化顺序讲透。顺带聊聊后台服务 HTTPS 证书自动化这件省心事。
支持通配符SSL证书、多域名证书、IP证书。适配ACME接口, 支持Zerossl、Let's Encrypt和Google等渠道。登录已有账号
2026-05-24 08:04:45 MySQL datetime 时间处理 Java
最近帮同事查一个订单创建时间丢时分秒的 bug,代码里明明设了 14:30:45,落库后变成 2024-05-20 00:00:00。
最后发现 ORM 映射里把字段写成了 java.sql.Date——这个坑我自己也踩过不止一次,刚好趁这次把 datetime 相关的常用操作和容易出问题的地方整理一下。
先看建表。需要记录完整时间点的时候,字段类型就设成 DATETIME,格式是 YYYY-MM-DD HH:MM:SS,范围从 1000-01-01 00:00:00 到 9999-12-31 23:59:59。
跟 date(只有日期)和 time(只有时间)的区别就在这里。
CREATE TABLE t_order (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_no VARCHAR(50) NOT NULL,
create_time DATETIME NOT NULL COMMENT '创建时间(含时分秒)'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
往这个字段里插数据,最省事的办法就是 NOW(),自动拿当前日期时间,带全时分秒:
INSERT INTO t_order (order_no, create_time)
VALUES ('ORD2024052001', NOW());
如果要插一个指定的时间,直接写标准格式的字符串就行,MySQL 会自动把它转成 datetime,不需要在外面多套一层 DATE_FORMAT:
INSERT INTO t_order (order_no, create_time)
VALUES ('ORD2024052003', '2024-05-20 14:30:45');
除非你的时间字符串格式比较怪,比如先拿到的是一串数字或非标准分隔符,那可以用 DATE_FORMAT 转一下:
INSERT INTO t_order (order_no, create_time)
VALUES ('ORD2024052002', DATE_FORMAT('2024-05-20 14:30:45', '%Y-%m-%d %H:%i:%s'));
不过多数情况下第一种就够了。另外,如果希望插入时自动填当前时间,定义字段的时候加上 DEFAULT CURRENT_TIMESTAMP,之后 INSERT 语句可以省掉这个字段:
CREATE TABLE t_order (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_no VARCHAR(50) NOT NULL,
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO t_order (order_no) VALUES ('ORD2024052004');
查询出来就是完整的 2024-05-20 14:30:45 格式,没啥特别的。想定制显示的话,用 DATE_FORMAT:
SELECT
order_no,
DATE_FORMAT(create_time, '%Y年%m月%d日 %H时%i分%s秒') AS formatted_time
FROM t_order;
单拿小时、分钟、秒可以用 HOUR()、MINUTE()、SECOND():
SELECT
order_no,
HOUR(create_time) AS hour,
MINUTE(create_time) AS minute,
SECOND(create_time) AS second
FROM t_order;
不过这些都是 SQL 层面的事。
真正容易出问题的,其实是应用代码跟数据库之间的类型映射。开头说的那种问题,在 Java 里的标准修法是这样——如果你用的是 Java 8 以上,直接用 java.time.LocalDateTime,通过 JDBC 驱动的 setObject 传进去就行:
LocalDateTime createTime = LocalDateTime.of(2024, 5, 20, 14, 30, 45);
ps.setObject(2, createTime);
还在用旧版本 Java 的话,就改用 java.sql.Timestamp:
Timestamp timestamp = Timestamp.valueOf("2024-05-20 14:30:45");
ps.setTimestamp(2, timestamp);
千万别用 java.sql.Date,那个只有日期部分,时分秒会全丢。
另一个高频问题是时区。Java 里明明时间是对的,进到库就少了 8 小时,或者多了。
这种情况大概率是 JVM 跟 MySQL 的时区没对齐。可以在 MySQL 那边设东八区:
SET GLOBAL time_zone = '+8:00';
SET SESSION time_zone = '+8:00';
或者在 JDBC 连接串里显式指定,这个更常用:
jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=Asia/Shanghai
实际项目里,我习惯把这几个点统一处理:数据库字段就用 DATETIME,Java 侧固定用 LocalDateTime,连接串带上 serverTimezone=Asia/Shanghai,SQL 一律参数绑定不拼字符串。这样类型映射和时区一般不会再折腾你。需要范围查询的 datetime 字段,比如按时间段拉订单,该加索引就加一个:
CREATE INDEX idx_create_time ON t_order(create_time);
基本就这些。时间类型出的问题排查起来往往很绕,但最后发现都落在类型映射和时区配置上,提前统一掉能省很多半夜报警。
上一篇: Ubuntu 下动态链接库路径的折腾记录
从一次线上配置读不到的诡异问题说起,梳理 ExtClassLoader 的边界与系统类加载器读配置的正确姿势,再走一遍 new 一个对象背后从类加载到构造方法执行的完整链路,并用父子类 demo 把初始化顺序讲透。顺带聊聊后台服务 HTTPS 证书自动化这件省心事。
分享 Fastjson 反序列化非标准日期格式的问题,以及通过 @JSONField 注解指定格式的解决方法
线上遇到这个报错,通常就是Java运行环境版本比编译时用的版本低,整理了原因和两种实际可用的处理方式。