关于java中Date类型和mysql中DATETIME类型时间精度问题
一、现象
项目中需要电量计算,当前时间的电量-开始时间的电量=这个时间段的用电量。如果当前时间是整点,需要计算一次上个时间段的电量。代码如下:
PowerDataQueryReqDTO powerDataQueryReqDTO=new PowerDataQueryReqDTO();
powerDataQueryReqDTO.setDeviceId(req.getDeviceId());
powerDataQueryReqDTO.setMerchantId(req.getMerchantId());
LocalDateTime queryStartTime = LocalDateTime.ofInstant(req.getCreateTime().toInstant(), ZoneId.systemDefault());
//电量统计表,按照小时是0-23时,如果createTime是凌晨0时0分0秒,查询的开始时间是昨天的23时;如果createTime是凌晨0分0秒,查询的开始时间是上个小时
if(queryStartTime.getMinute() == 0 && queryStartTime.getSecond() == 0){
queryStartTime = queryStartTime.minusSeconds(1);
}
int year = queryStartTime.getYear();
int month = queryStartTime.getMonthValue();
int day = queryStartTime.getDayOfMonth();
int hour = queryStartTime.getHour();
LocalDate queryDate = queryStartTime.toLocalDate();
//当时电量统计
String hourStartTime = queryDate + " "+ hour +":00:00";
powerDataQueryReqDTO.setTime(hourStartTime);
BigDecimal powerByHour = getCurrentPowerByParam(req, powerDataQueryReqDTO);
如果当前数据的createTime是0分0秒,但是毫秒不是0,本来应该归于这个小时的用电量,却归到上个小时,导致用电量计算不准确。数据库保存的createTime类型是DATETIME,值是0分1秒。
二、原因
java中的Date精度到毫秒,忽略了毫秒,所以本该归到这个时间段的电量,算到了上个时间段。mysql数据库DATETIME类型没有指定精度,默认保存到秒,存储的时候毫秒会四舍五入。数据库DATETIME类型精度如下:
DATETIME(0):只存储到秒级别的精度,不包含小数秒。例如,2025-01-24 12:34:56
DATETIME(3):存储到毫秒级别的精度。例如,2025-01-24 12:34:56.789
DATETIME(6):存储到微秒级别的精度。例如,2025-01-24 12:34:56.123456
三、解决方法
通过java应用程序层面,生成createTime时将毫秒设置成0,代码如下:
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
calendar.set(Calendar.MILLISECOND,0);
threePhaseMeterVO.setCreateTime(calendar.getTime());
或者加上毫秒的判断
if(queryStartTime.getMinute() == 0 && queryStartTime.getSecond() == 0 && queryStartTime.getNano()==0){
queryStartTime = queryStartTime.minusSeconds(1);
}
本来想数据库保存DATETIME类型时候舍弃毫秒部分,但是没有找到全局的配置。可通过如下方式解决:
1、在应用程序代码中将时间值截断到秒级别,然后再插入数据库。
2、在SQL语句中使用MySQL的内置函数来截断时间值。例如:DATE_FORMAT(NOW(3), ‘%Y-%m-%d %H:%i:%s’)
3、触发器,在每次插入或更新记录时自动截断时间值。