안녕하세요. J4J입니다.
이번 포스팅은 LocalDateTime 사용하는 방법에 대해 적어보는 시간을 가져보려고 합니다.
LocalDateTime이란?
LocalDateTime은 Java8에서 등장한 클래스 중 하나로 날짜와 관련된 다양한 기능들을 수행할 때 활용할 수 있습니다.
Spring을 이용하여 개발 할 때 날짜 타입을 생성하면 보통 Date를 사용해 볼 수 있습니다.
하지만 LocalDateTime이 등장하면서 부터 Date 대신 LocalDateTime의 사용을 권장하고 있습니다.
왜냐하면 Date는 mutable(가변성)하기 때문에 Date 값을 새로운 변수에 담아 값을 수정하게 되면 원본도 함께 변경이 이루어지지만 LocalDateTime은 immutable(불변성)이기 때문에 원본은 변경이 이루어지지 않기 때문입니다.
// Data
package com.localdatetime.compare;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
import java.time.LocalDateTime;
import java.util.Date;
@ToString
@Getter
@AllArgsConstructor
public class Data {
private Date date;
private LocalDateTime localDateTime;
}
// Compare
package com.localdatetime.compare;
import java.time.LocalDateTime;
import java.util.Date;
public class Compare {
public static void main(String[] args) {
Data data = new Data(new Date(), LocalDateTime.now());
Date newDate = data.getDate();
LocalDateTime newLocalDateTime = data.getLocalDateTime();
newDate.setTime(1695476838988L); // 특정 시간대로 변경
newLocalDateTime = newLocalDateTime.plusDays(3); // 3일 후로 변경
System.out.println(data.getDate()); // Sat Sep 23 22:47:18 KST 2023
System.out.println(newDate); // Sat Sep 23 22:47:18 KST 2023 (원본과 동일)
System.out.println(data.getLocalDateTime()); // 2023-09-23T22:55:02.129660700
System.out.println(newLocalDateTime); // 2023-09-26T22:55:02.129660700 (원본과 다름)
}
}
그래서 Date를 사용하게 되면 의도하지 않은 변경점이 발생될 수 있습니다.
그러나 LocalDateTime을 사용하게 되면 Date와 달리 의도하지 않은 변경점이 발생되지 않기 때문에 LocalDateTime의 사용을 권장하고 있습니다.
LocalDateTime Method
LocalDateTime에는 자체적으로 유용하게 사용 가능한 여러 메서드들을 내장하고 있습니다.
날짜를 변경하는 것부터 포매팅과 관련된 기능들도 편리하게 활용해 볼 수 있습니다.
많은 기능들 중 주로 사용될만한 기능들을 정리하면 다음과 같습니다.
package com.localdatetime.util;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class LocalDateTimeUtil {
public static void main(String[] args) {
LocalDateTime localDateTime = LocalDateTime.now(); // 2023-09-23T23:09:06.762688200
// 일 변경
localDateTime.plusDays(3);
localDateTime.minusDays(3);
// 월 변경
localDateTime.plusMonths(3);
localDateTime.minusMonths(3);
// 년 변경
localDateTime.plusYears(3);
localDateTime.minusYears(3);
// 시간 변경
localDateTime.plusHours(3);
localDateTime.minusHours(3);
// 분 변경
localDateTime.plusMinutes(3);
localDateTime.minusMinutes(3);
// 초 변경
localDateTime.plusSeconds(3);
localDateTime.minusSeconds(3);
// 원하는 타입의 값을 조회
localDateTime.getDayOfMonth(); // 현재 월 중 몇일인지 확인 (ex, 23/09/23이면 23을 반환)
localDateTime.getDayOfYear(); // 현재 년 중 몇일인지 확인 (ex, 23/09/23이면 266를 반환)
localDateTime.getDayOfWeek(); // 현재 주차 중 어떤 요일인지 확인 (ex, 23/09/23이면 SATURDAY를 반환)
localDateTime.getMonthValue(); // 월
localDateTime.getYear(); // 년
localDateTime.getHour(); // 시간
localDateTime.getMinute(); // 분
localDateTime.getSecond(); // 초
// 포맷팅
localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")); // 2023-09-23 11:08:23
localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); // 2023-09-23
localDateTime.format(DateTimeFormatter.ofPattern("yyyy.MM.dd hh,mm,ss")); // 2023.09.23 11,08,23
localDateTime.format(DateTimeFormatter.ofPattern("yyyy/MM/dd hh:mm:ss:SSS")); // 2023/09/23 11:08:49:087
}
}
GetMapping에서 사용 방법
LocalDateTime을 Spring 내부에서 활용할 때 위의 메서드들을 이용하면 사용하는데 웬만하면 문제가 발생되지 않습니다.
그러면 이제 고민해봐야 되는 것은 Client와 통신되는 과정입니다.
Client와 통신될 때 날짜 데이터를 전달받는 방법은 크게 query로 전달받는 것과 body로 전달받는 것이 있습니다.
그중 query로 전달받는 경우, 특히 GetMapping에서 활용할 때 어떻게 해야 되는지에 대해 소개해드리겠습니다.
query로 전달 받는 경우는 @DateTimeFormat을 활용할 수 있습니다.
@DateTimeFormat에 Client로부터 전달받을 날짜 데이터의 포맷 패턴을 정의하고 Client에서는 해당 포맷 패턴에 맞게 데이터를 보낸다면 정상적으로 통신이 이루어질 수 있습니다.
package com.localdatetime.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
@RestController
@Slf4j
public class LocalDateTimeController {
@GetMapping("/get")
public Object get(@DateTimeFormat(pattern = "yyyy/MM/dd HH:mm:ss") LocalDateTime date) {
log.info(date.toString()); // 2023-09-21T23:12:11
return ResponseEntity.ok(date);
}
}
여기서 주의할 점은 LocalDateTime으로 전달받기 위해서는 년/월/일/시 데이터들이 모두 들어있어야 합니다.
예를 들어, 만약 다음과 같이 년/월/일만 포맷 패턴으로 설정하고 Client에서 패턴에 맞게 전달한다고 하더라도 요청이 올바르게 수행되지 않습니다.
package com.localdatetime.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
@RestController
@Slf4j
public class LocalDateTimeController {
@GetMapping("/get")
public Object get(@DateTimeFormat(pattern = "yyyy/MM/dd") LocalDateTime date) {
log.info(date.toString());
return ResponseEntity.ok(date);
}
}
@DateTimeFormat 전역 설정
GetMapping에서 넘어오는 query 데이터들에 대해 날짜 포맷팅을 할 때 위와 같이 설정을 해볼 수 있습니다.
하지만 개발을 하다보면 프로젝트에서 날짜 데이터를 한 곳에서만 받는 것이 아니고 정말 다양한 곳에서 활용이 될 텐데 매번 @DateTimeFormat 패턴 설정을 하는 것은 불필요한 작업일 수 있습니다.
만약, 모든 API에서 전달받는 날짜 데이터들의 포맷 패턴을 동일하게 할 것이라면 다음과 같이 한 번의 설정만 하는 것으로 변경해 볼 수 있습니다.
[ 1. LocalDateTimeFormatter 클래스 생성 ]
package com.localdatetime.formatter;
import org.springframework.format.Formatter;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class LocalDateTimeFomatter implements Formatter<LocalDateTime> {
@Override
public LocalDateTime parse(String text, Locale locale) {
return LocalDateTime.parse(text, DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss", locale));
}
@Override
public String print(LocalDateTime object, Locale locale) {
return DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss", locale).format(object);
}
}
[ 2. LocalDateTimeConfig 클래스 생성 ]
package com.localdatetime.config;
import com.localdatetime.formatter.LocalDateTimeFomatter;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class LocalDateTimeConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(new LocalDateTimeFomatter());
}
}
[ 3. @DateTimeFormat 삭제 ]
package com.localdatetime.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
@RestController
@Slf4j
public class LocalDateTimeController {
@GetMapping("/get")
public Object get(LocalDateTime date) {
log.info(date.toString()); // 2023-09-21T23:12:11
return ResponseEntity.ok(date);
}
}
[ 4. 테스트 ]
위와 같이 설정하고 다시 동일하게 API를 요청해보면 다음과 같이 정상적으로 동작하는 것을 확인할 수 있습니다.
그러면 이제부터는 매번 query를 전달 받는 곳마다 @DateTimeFormat 사용하는 것을 제거해 줄 수 있습니다.
PostMapping에서 사용 방법
이번에는 body로 전달받는 것을 해보겠습니다.
body로 전달받는 대표적인 케이스는 PostMapping이 될 수 있습니다.
body에 담겨 데이터가 넘어올 때는 query때와 달리 @JsonFormat을 활용해야 합니다.
그 외는 모두 동일합니다.
Client에서 정해진 패턴에 맞게 데이터를 담아 요청해야하고 또한 년/월/일/시 까지 패턴이 적용되어야 합니다.
// PostDto
package com.localdatetime.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Getter;
import lombok.ToString;
import java.time.LocalDateTime;
@ToString
@Getter
public class PostDto {
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy/MM/dd HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime date;
}
// Controller
package com.localdatetime.controller;
import com.localdatetime.dto.PostDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class LocalDateTimeController {
@PostMapping("/post")
public Object post(@RequestBody PostDto postDto) {
log.info(postDto.getDate().toString()); // 2023-09-21T23:12:11
return ResponseEntity.ok(postDto.getDate());
}
}
@JsonFormat 커스텀 어노테이션 설정
body쪽도 query와 마찬가지로 날짜 타입의 데이터가 한 곳에서만 사용되는 것이 아니라 여러 API에서 활용될 것입니다.
즉, 매번 포맷 패턴 등에 대한 정보들을 계속 넣어줘야 합니다.
이런 문제를 조금이라도 개선하고자 body 쪽도 모두 동일한 날짜 포맷 패턴을 사용할 경우 커스텀 어노테이션을 생성하여 조금 더 편리하게 활용해 볼 수 있습니다.
[ 1. 커스텀 어노테이션 생성 ]
package com.localdatetime.annotation;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.lang.annotation.*;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy/MM/dd HH:mm:ss", timezone = "Asia/Seoul")
public @interface LocalDateTimeFormat {
}
[ 2. @JsonFormat 삭제 ]
package com.localdatetime.dto;
import com.localdatetime.annotation.LocalDateTimeFormat;
import lombok.Getter;
import lombok.ToString;
import java.time.LocalDateTime;
@ToString
@Getter
public class PostDto {
@LocalDateTimeFormat
private LocalDateTime date;
}
[ 3. 테스트 ]
위와 같이 설정을 한 후 다시 API를 요청해보면 다음과 같이 이전과 동일하게 나오는 것을 확인할 수 있습니다.
그러면 이제부터 body로 날짜 데이터가 넘어올 때 @JsonFormat대신 @LocalDateTimeFormat을 사용하여 보다 간편하게 적용해 볼 수 있습니다.
이상으로 LocalDateTime 사용하는 방법에 대해 간단하게 알아보는 시간이었습니다.
읽어주셔서 감사합니다.
'Spring > SpringBoot' 카테고리의 다른 글
[SpringBoot] Spring Boot 3 버전 이후 gradle에 querydsl 사용 환경 설정하기 (0) | 2024.01.22 |
---|---|
[SpringBoot] @Transactional 사용 방식 정리 (1) | 2023.09.25 |
[SpringBoot] i18n을 활용하여 다국어 처리하기 (0) | 2023.09.18 |
[Springboot] Jacoco를 이용하여 테스트 커버리지 확인하기 (1) | 2023.09.16 |
[SpringBoot] SpringBatch 사용하기 (4) - Quartz로 클러스터링 처리하기 (1) | 2023.09.10 |
댓글