일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- @EntityListeners
- 1*1000
- MYSQL
- sendFractionalSeconds
- MSSQL
- 오블완
- mysql not equal null
- RootGraph
- spring boot
- 티스토리챌린지
- NamedEntityGraph
- pooled-lo
- Protecle
- mysql equal null
- EmbeddedId
- getEntityGraph
- +9:00
- getDateCellValue
- 버전 문자열 비교
- load order
- @CreateDate
- fractional seconds
- createEntityGraph
- deserializer
- apatch poi
- AuditingEntityListener
- https
- yml
- mysql =
- 운동해서 광명찾자
- Today
- Total
Hello
어플리케이션에서 시간 처리 시 반올림 되는 경우 본문
MySql 에서 소수초를 사용하지 않는 시간 데이터 타입의 경우 LocalTime.MAX 시간으로 데이터 생성 시 다음날 00:00:00 로 저장되는 이슈가 있었고 초 단위 이하 값을 다루지 않는 데이터였기에 23:59:59 로 저장하도록 수정 대응한적이 있습니다. ex ) 2023-09-05 23:59:59.999... -> 2023-09-06 00:00:00
또한, LocalDateTime.now()를 사용하는 경우 현재 날짜 시간을 생성하기 때문에 소수초가 반올림 될 수도 있습니다.
ex) 2023-09-05 13:13:45.584668789 -> 아래 이미지
소수 초를 쓰는 데이터 타입의 경우 값에 따라 소수 초단위 or 초단위로 반올림 될 수 있고,
소수 초를 쓰지 않는 데이터 타입의 경우 초 단위로 반올림 될 수 있습니다.
추후, 조회 시 초 단위 미만에 대한 처리가 필요한 경우 23:59:59 ~ 00:00:00 사이의 데이터가 누락될 수 있는 문제가 있습니다.
이를 어플리케이션에서 대응 할 수 있는 방법을 정리하고자 합니다.
- 사용 기술 스택
- java 8
- spring boot 2
- mysql 5.7
MySql Connector/J가 데이터 바인딩 시 초 단위 미만에 대한 처리를 합니다. 아래 과정을 살펴보겠습니다.
반올림 처리되는 과정
public void setTimestamp(int parameterIndex, Timestamp x) throws java.sql.SQLException {
synchronized (checkClosed().getConnectionMutex()) {
int fractLen = -1;
if (!this.sendFractionalSeconds || !this.serverSupportsFracSecs) {
fractLen = 0;
} else if (this.parameterMetaData != null && parameterIndex <= this.parameterMetaData.metadata.fields.length && parameterIndex >= 0) {
fractLen = this.parameterMetaData.metadata.getField(parameterIndex).getDecimals();
}
setTimestampInternal(parameterIndex, x, null, this.connection.getDefaultTimeZone(), false, fractLen,
this.connection.getUseSSPSCompatibleTimezoneShift());
}
}
- fracLen : 소수 초 길이
- sendFractionalSeconds or serverSupportsFracSecs 값이 false인 경우 소수 초를 사용하지 않도록 ( fracLen = 0 )
- sendFractionalSeconds 기본값은 true 입니다.
- this.setTimestampInternal() 호출
PreparedStatement.setTimestampInternal() 일부
protected void setTimestampInternal(int parameterIndex, Timestamp x, Calendar targetCalendar, TimeZone tz, boolean rollForward, int fractionalLength,
boolean useSSPSCompatibleTimezoneShift) throws SQLException {
if (x == null) {
setNull(parameterIndex, java.sql.Types.TIMESTAMP);
} else {
checkClosed();
x = (Timestamp) x.clone();
if (!this.serverSupportsFracSecs || !this.sendFractionalSeconds && fractionalLength == 0) {
x = TimeUtil.truncateFractionalSeconds(x);
}
if (fractionalLength < 0) {
// default to 6 fractional positions
fractionalLength = 6;
}
x = TimeUtil.adjustTimestampNanosPrecision(x, fractionalLength, !this.connection.isServerTruncatesFracSecs());
...
if (fractionalLength > 0) {
int nanos = x.getNanos();
if (nanos != 0) {
buf.append('.');
buf.append(TimeUtil.formatNanos(nanos, this.serverSupportsFracSecs, fractionalLength));
}
}
- fractionalLength (=fractLen) 이 조건에 따라 MySql이 지원하는 소수 초 최대 길이인 6으로 설정합니다.
- 반올림을 처리하는 TimeUtil.adjustTimestampNanosPrecision()호출합니다.
TimeUtil.adjustTimestampNanosPrecision()
- fsp = fractionalLength(6) 범위가 올바른지 확인하는 조건을 통과합니다.
- tail 계산 Math.pow(10, 3) = 10^3 = 1000.0
- serverRoundFracSecs는 메소드 호출 시 !this.connection.isServerTruncatesFracSecs() 를 넘겨 받는다.
- serverTruncatesFracSecs 기본값은 false 이나, !를 붙여 true를 넘긴다.
- nanos = Math.round(nanos/1000.0) * 1000 = 1000000000
- nanos %= 1000000000 = 0
- res.getTime() + 1000 = 기존 시간 + 1초
- setNanos(nanos) 0 넣어줌
소수 초 생략
초 단위 미만에 대한 처리 과정을 살펴 보았는데요. 초 단위 미만에 대한 값을 생략 하도록 설정하는 방법도 있습니다.
sendFractionalSeconds = false
5.x 버전에서는 sendFractionalSeconds 를 false로 작성하는 것입니다.
- 해당 설정은 전역적으로 적용되므로 선택적 사용이 불가능
- 초 단위 이하는 버림으로 반올림 문제는 생기지 않음
- 쿼리를 날릴때, 파라미터에 datetime 관련 타입의 fractional seconds를 사용할 수 없습니다.
url:jdbc:mysql:// ~ ?sendFractionalSeconds=false
MySql Connector/j 에 설정되는 datetime type config properties 중 소수 초 관련 옵션
5.x | sendFractionalSeconds | "false"로 설정하면 데이터를 서버에 보내기 전에 소수 초가 항상 잘립니다. default : true |
8.x | sendFractionalSecondsForTime | "false"로 설정하면 JDBC 사양에서 요구하는 대로 'java.sql.Time'의 소수 초가 무시됩니다. "true"로 설정하면 해당 값은 MySQL TIME 열에 밀리초를 저장할 수 있도록 소수 초로 렌더링됩니다. default : true |
위 설정을 한 후 코드를 다시 살펴보겠습니다.
소수 초를 버릴 수 있도록 fractLen = 0 처리
TimeUtil.truncateFractionalSeconds 내부를 보면 setNanos(0) 강제로 0으로 셋팅해주는 것을 확인할 수 있습니다.
public static Timestamp truncateFractionalSeconds(Timestamp timestamp) {
Timestamp truncatedTimestamp = new Timestamp(timestamp.getTime());
truncatedTimestamp.setNanos(0);
return truncatedTimestamp;
}
결론 : 필요한 경우에 따라 고민을 해보아야할 것 같다.
- 초 단위 미만이 필요하지 않은 경우
- DB에서 fractional seconds를 사용하는 곳이 없다면, false 설정을 통해 날짜 반올림 때문에 발생하는 문제도 해결할 수 있습니다.
- sendFractionalSeconds=false
- 초 단위 미만의 처리가 필요한 경우
- 데이터가 초 단위 미만으로 쌓이는 경우 조회하기위해 아래와 같이 사용이 필요하다.
- LocalTime.of(23, 59, 59, 999_999)
참고 : https://dev.mysql.com/doc/connector-j/8.1/en/connector-j-reference-configuration-properties.htmlhttps://dev.mysql.com/doc/connector-j/8.1/en/connector-j-connp-props-datetime-types-processing.html#cj-conn-prop_sendFractionalSeconds
'DB' 카테고리의 다른 글
[MySQL] not equal 검색 null 제외 (0) | 2023.12.19 |
---|---|
[SQL SERVER] limitation of 2100 items for parameters (0) | 2023.09.22 |
MySql 버전에 따른 JDBC 설정 (0) | 2023.09.01 |
MySql datetime 반올림 (0) | 2023.08.30 |
[MySQL] 특정 조건일 때 join이 필요한 경우 (0) | 2023.07.28 |