当然,关于创建时间多一秒少一秒本身可能有不太重要,但是关于时间的查询,绝对就是一个比较严重的问题了。
有时候数据特殊性导致很难发现多一条少一条很难发现,但是少一条数据可能就是非常严重的问题。
本文,以MySQL和MyBatis为例,来说一下其中的关于时间的一些非常容易被忽略的小问题。
create table datetime_timestamp (
id int(20) unsigned not null auto_increment COMMENT '主键ID',
datetime0 datetime COMMENT '没有小数部分,支持到秒',
datetime3 datetime(3) COMMENT '3位小数,毫秒',
datetime6 datetime(6) COMMENT '6位小数,微妙',
timestamp0 TIMESTAMP COMMENT '没有小数部分,支持到秒',
timestamp3 TIMESTAMP(3) COMMENT '3位小数,毫秒',
timestamp6 TIMESTAMP(6) COMMENT '6位小数,微妙',
primary key (`id`)
) engine = InnoDB auto_increment = 1 default CHARSET = utf8mb4 collate = utf8mb4_unicode_ci COMMENT = '时间';
// select * from datetime_timestamp where datetime0 = #{date}
// datetime0的数据类型是MySQL datetime
// DatetimeTimestampMapper datetimeTimestampMapper
List<DatetimeTimestampTO> listDatetime0ByDate(java.util.Date date);
@Resource
private DatetimeTimestampMapper datetimeTimestampMapper;
@Test
void listDatetime0ByDate() {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.YEAR, 2023);
calendar.set(Calendar.MONTH, 11);
calendar.set(Calendar.DATE, 31);
calendar.set(Calendar.HOUR_OF_DAY, 12);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
Date date = calendar.getTime();
List<DatetimeTimestampTO> tos = datetimeTimestampMapper.listDatetime0ByDate(date);
tos.forEach(System.out::println);
}
上面的数据查询能查询到id为5的那一条数据吗?
产生这种结果的原因是什么?
答案是:极小概率能查到
为什么呢?
要知道上面问题的答案,得先搞清楚另一个问题:
java.util.Date类型和MySQL的datetime类型绝对匹配吗?
答案是不能,java.util.Date精度是毫秒,datetime默认是datetime(0),精度是秒。
select * from datetime_timestamp where datetime0 = #{date}
显然只有当java.util.Date的毫秒为0的时候才能匹配上。
我们可以看一下,MyBatis打印的SQL:
我们可以看到MyBatis将java.util.Date被转换为了java.sql.Timestamp
为什么会这样呢?
因为java.sql.PreparedStatement不支持java.util.Date,只支持java.sql.Date:
执行转换的是org.apache.ibatis.type.DateTypeHandler:
将毫秒部分设置为0,就可以
calendar.set(Calendar.MILLISECOND, 0);
@Resource
private DatetimeTimestampMapper datetimeTimestampMapper;
@Test
void listDatetime0ByDate() {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.YEAR, 2023);
calendar.set(Calendar.MONTH, 11);
calendar.set(Calendar.DATE, 31);
calendar.set(Calendar.HOUR_OF_DAY, 12);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
Date date = calendar.getTime();
List<DatetimeTimestampTO> tos = datetimeTimestampMapper.listDatetime0ByDate(date);
tos.forEach(System.out::println);
}
为啥我们查询基本很少发现时间查询有问题呢?
因为很多时候,时间更多的时候是查询的范围,要么就是查询到日期,不涉及到时间毫秒、微妙这种。
不是,插入也有问题,插入的时候,如果大于等于500毫秒,会向前进1,小于500毫秒会被舍弃:
有兴趣的朋友,可以自己动手尝试一下这个。
MySQL的TIME、DATETIME、TIMESTAMP支持小数秒,最高精度能到微妙(6为小数)
FSP(fractional seconds precision)
CREATE TABLE time_table(time_v TIME(3), datetime_v DATETIME(6), timestamp_v TIMESTAMP(4));
对应TIME类型,java中可以使用java.sql.Time,很遗憾它只支持到毫秒,不能到微妙,如果需要到微妙需要自己实现,或者使用java.time.LocalTime,它支持到纳秒。
对于DATETIME、TIMESTAMP类型java中可以使用java.sql.Date,但是只能支持到毫秒,可以使用java.time.LocalDateTime或java.sql.Timestamp类型,可以支持到纳秒。
@Test
void listDatetime0ByTimestamp() {
Timestamp timestamp = new Timestamp(System.currentTimeMillis());
timestamp.setNanos(517024000);
List<DatetimeTimestampTO> tos = datetimeTimestampMapper.listDatetime6ByTimestamp(timestamp);
tos.forEach(System.out::println);
}