배경
사람이 살다보... 아니 개발자가 살다보면 특정 일의 첫시간,끝시간을 구할 일이 있습니다.
java8 이후부턴가? 나온 localDateTime
을 이용해서 구해본다면 코드는 다음과 같습니다
public static Timestamp getEndOfDay(Timestamp t) {
LocalDateTime dateTime = new LocalDateTime(t);
// 한날의 최후시간인 23시 59분 59.999 초로 세팅하면 DB 에 저장하는 순간 그 다음날 0시0분0초 000 으로 바뀐다.
// 날짜가 바뀌게 되어서 엉망진창이 될 수 있으니 조심하자. 1초를 빼줄 필요가 있다
dateTime = dateTime.millisOfDay().withMaximumValue().minusSeconds(1);
return new Timestamp(dateTime.toDateTime().getMillis());
}
문제는 저 minusSeconds 부분인데 저걸 저희가 굳이 넣어준 이유는 다음과 같습니다.
한날의 최후시간인 23시 59분 59.999 초로 세팅하면 DB 에 저장하는 순간 그 다음날 0시0분0초 000 으로 바뀐다.
일전에 백오피스 클라이언트에서 어떤 상품의 판매기간 세팅시 사용했던 캘린더 라이브러리가, 그 날의 끝시간을 던질때 23:59:59.999 값으로 던졌었는데, 자꾸 판매 일시(date) 데이터를 뽑아보니 입력한 날이 아닌 다음날로 나오길래 어떤 오류인가 봤더니 어플리케이션단에서만 해도 2019-12-31 23:59:59
였던 시간값이 DB 에 저장되니 다음날인 2020-01-01 00:00:00
으로 바뀌어버렸습니다.
원인
찾아보니 MsSQL 같은 경우는 아래와 같은 룰이 있었습니다.
Java
에서 Timestamp
로 지정된 필드가 MySQL
에 ORM
으로 매핑될 때, Datetime
타입으로 자동 생성이 되었습니다. (현재 버전에서는 모르겠지만 예전엔 그랬음) 그런데 Datetime
은 위와 같은 룰로 인해서 Timestamp
값과 미묘하게 달라질 때가 있었습니다.
squelPro 라는 클라이언트 툴을 이용해 mysql 에 시간값 뒤에 소수점을 넣어줬더니... 그냥 1초 늘려 버립니다. 결국 현재상태로는 59.999 는 위험합니다.
해결 방법
1. MySQL 필드타입도 Timestamp 로 바꾸기
사실 명확한 해결책은 MySQL 필드타입도 Timestamp 로 바꾸는 것으로 보이지만,, 수백개의 레거시 필드를 당장 바꾸는것은 어려운 일이기에 ㅜ 이 방법은 포기했습니다.
2. milli sec 단위를 삭제해버리기
클라이언트에서 혹시 하루의 끝값을 받을 일이 있다면, 넘겨줄때도 .999는 안붙게 할 수도 있고.그리고 서버에서 핸들링할때도 끝날짜로 넣어줄땐 꼭 .999 가 붙지 않았나 확인해줄 수 있습니다.
그냥 단위를 between 처럼 쓰면 1.1 ~ 2.1 이라고 해도 2.1 은 포함 안되니까 괜찮은거 아니냐는 의견도 있었는데, 사실 client 에서 일반 유저들이 기간을 검색할땐 1월1일부터 1월31일까지로 검색하지 2월1일은 포함하지 않습니다. 그래서 결국은 client 개발자가 31 의 밀리세컨즈 값을 절삭하던, 그 값을 2월1일 0시 0분 0초로 바꿔서 넘겨주던 작업은 필요한 상황이었습니다.
3. 새롭게 처음부터 잘 만들기
우리 코드는 레거시 코드들이 살아있다보니 이런 어려움을 겪었지만, 처음부터 java8 이후에 나온 값들을 잘 활용하면 어려움 없이 Java 시간객체와 DB 의 시간 컬럼을 맞출 수 있습니다. 요건 아래와 같은 글들에 자세한 내용이 있으니 참고하시면 좋을 듯 합니다.
Java 8 Date(Time) 와 JPA 그리고 스프링 부트 | Popit
결론
여러분 시간값은**MySQL 필드타입도 Timestamp 로 꼭 사용하세요 PLZ.. ㅜ**
결론적으로 우리는 Server 에서 절삭해서 넘겨주는 방식을 택했습니다. 단순히 검색용 파라미터에서 날짜를 던지는 때에는 날짜가 하루 뒤로 바뀌어도 되지만, 실제로 엔티티에 저장되는 값이 다른 날짜로 넘어가버리면 추후 데이터 분석등에서 Date 함수등을 써서 가져올때 날짜 자체가 바뀌게 되므로
큰 파장이 일으킬 수 있기에 다음 날짜를 쓰는 방식은 사용하지 않았습니다. (위의 예시처럼)
이런 이슈도 어쩌다 보면 생길 수 있다는것을 공유드리면서,,,
글로벌 서비스에서는 특히 더 크리티컬 할 수 있는 시간값이기에 timestamp 등을 사용할 시에는 더욱! 실제로 사용할 값의 체계를 잘 이해하고 설계때부터 클라이언트에서의 타입, 서버에서의 타입, Database 에서의 타입이 다 활용가능하게 잡는 것이라는 것을 다시 한번 느끼며... fin.