평소에 Token을 만들때 IssuedAt에 Date 타입을 User의 LocalDateTime 타입으로 전환하려고 할때 또는 LocalDateTime을 사용해서 Date 타입의 필드에 넣으려고 할 때 항상 애를 먹었다.
그래서 타입 변환에 필요한 메서드들을 다음번엔 이 글을 보고 바로 처리할 수 있게 차례대로 정리해 두려고 한다.
또한 직렬화/역직렬화에서 생겼던 문제를 다뤄볼것이다.
1. Date → LocalDate, LocalDateTime 변환
1-1. Date → LocalDate 변환
Date 객체를 LocalDate로 변환하려면, 먼저 Instant로 변환한 후 ZoneId를 지정하여 ZonedDateTime으로 변환한 뒤 toLocalDate()를 호출한다.
코드 예제
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Date;
public class DateToLocalDateExample {
public static void main(String[] args) {
Date date = new Date();
LocalDate localDate = date.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDate();
System.out.println("Date: " + date);
System.out.println("LocalDate: " + localDate);
}
}
1-2. Date → LocalDateTime 변환
LocalDateTime으로 변환하려면 toLocalDateTime()을 호출하면 된다.
코드 예제
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
public class DateToLocalDateTimeExample {
public static void main(String[] args) {
Date date = new Date();
LocalDateTime localDateTime = date.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDateTime();
System.out.println("Date: " + date);
System.out.println("LocalDateTime: " + localDateTime);
}
}
1-3. Java 9 이상에서는 ofInstant() 사용 가능
Java 9 이상에서는 ofInstant()를 사용하면 더 간결하게 변환할 수 있다.
코드 예제
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
public class Java9DateConversion {
public static void main(String[] args) {
Date date = new Date();
LocalDate localDate = LocalDate.ofInstant(date.toInstant(), ZoneId.systemDefault());
LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
System.out.println("LocalDate: " + localDate);
System.out.println("LocalDateTime: " + localDateTime);
}
}
2. LocalDateTime → Date 변환
2-1. java.sql.Timestamp.valueOf() 사용
LocalDateTime을 Date로 변환할 때 java.sql.Timestamp.valueOf()를 사용할 수 있다.
코드 예제
import java.time.LocalDateTime;
import java.util.Date;
public class LocalDateTimeToDateExample {
public static void main(String[] args) {
LocalDateTime localDateTime = LocalDateTime.now();
Date date = java.sql.Timestamp.valueOf(localDateTime);
System.out.println("LocalDateTime: " + localDateTime);
System.out.println("Date: " + date);
}
}
2-2. Date.from() + Instant 사용
LocalDateTime을 Date로 변환할 때 Instant을 이용하는 방법도 있다.
코드 예제
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
public class LocalDateTimeToDateExample {
public static void main(String[] args) {
LocalDateTime localDateTime = LocalDateTime.now();
Instant instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
Date date = Date.from(instant);
System.out.println("LocalDateTime: " + localDateTime);
System.out.println("Date: " + date);
}
}
3. LocalDate → Date 변환
3-1. java.sql.Date.valueOf() 사용
LocalDate를 Date로 변환할 때 java.sql.Date.valueOf()를 사용할 수 있다.
코드 예제
import java.time.LocalDate;
import java.util.Date;
public class LocalDateToDateExample {
public static void main(String[] args) {
LocalDate localDate = LocalDate.now();
Date date = java.sql.Date.valueOf(localDate);
System.out.println("LocalDate: " + localDate);
System.out.println("Date: " + date);
}
}
3-2. Date.from() + Instant 사용
LocalDate를 Date로 변환할 때 Instant을 이용하는 방법도 가능하다.
코드 예제
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Date;
public class LocalDateToDateExample {
public static void main(String[] args) {
LocalDate localDate = LocalDate.now();
Instant instant = localDate.atStartOfDay(ZoneId.systemDefault()).toInstant();
Date date = Date.from(instant);
System.out.println("LocalDate: " + localDate);
System.out.println("Date: " + date);
}
}
4. 변환 요약 정리
변환 유형방법
Date → LocalDate | date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate() |
Date → LocalDateTime | date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime() |
Date → LocalDate (Java 9 이상) | LocalDate.ofInstant(date.toInstant(), ZoneId.systemDefault()) |
Date → LocalDateTime (Java 9 이상) | LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()) |
LocalDateTime → Date | java.sql.Timestamp.valueOf(localDateTime) |
LocalDateTime → Date (Instant 방식) | Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()) |
LocalDate → Date | java.sql.Date.valueOf(localDate) |
LocalDate → Date (Instant 방식) | Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant()) |
• Java 8 이상에서는 Instant, ZoneId, ZonedDateTime을 활용하여 Date, LocalDate, LocalDateTime을 변환할 수 있다.
• Java 9 이상에서는 LocalDate.ofInstant() 및 LocalDateTime.ofInstant()를 사용하여 변환을 더 간결하게 처리할 수 있다.
• java.sql.Timestamp와 java.sql.Date는 Date와 호환이 가능하므로 변환 시 사용할 수 있다.
JAVA 8 LocalDateTime 직렬화/ 역직렬화 문제
난 자바17을 사용하지만 DTO에서 LocalDateTime 타입의 필드를 해결하는 과정에서 문제가 발생했다. WebClient에서 LocalDateTime 필드를 요청하여 반환받는 과정에서 해당 타입이 직렬화가 수행되지 않는 문제였다.
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TokenResponse implements Serializable {
private static final long serialVersionUID = 1L; // 버전 관리용 ID
private String token;
private LocalDateTime tokenIssuedAt;
}
이 직렬화 과정에서 내가 설정한 직렬화 타입은 Jackson 타입이였는데 LocalDateTime은 직렬화가 불가능한 모양이였다.
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type java.time.LocalDateTime not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling (through reference chain: com.sparta.jrkBoard.user.presentation.dto.response.UserResponseDto["tokenIssuedAt"])
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77) ~[jackson-databind-2.18.2.jar:2.18.2]
해결 방법
2가지 의존성을 추가 해준다.
jsr310은 Java8에서 날짜 및 시간에 대한 API를 확장하는 라이브러리라고 한다. LocalDateTime, LocalDate, ZoneDateTime 등과 같은 Java8 날짜/시간 개체를 JSON data로 직렬화 및 역직렬화 할 수 있다.
databaind 는 JSON구조 데이터에 대한 바인딩을 Java객체에 제공하는 라이브러리이다. Json타입으로 받아오는 데이터들을 Java 타입으로 역직렬화 또는 반대로 직렬화도 가능하게 해준다.
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
implementation 'com.fasterxml.jackson.core:jackson-databind'
아래와 같이 직렬화와 역직렬화에 대해서 어떻게 할지 설정하거나 Format도 설정할 수 있다.
JsonProperty 어노테이션의 용도
- Json필드명과 Java필드명이 다를 때 맵핑하기 위해 사용한다.
- 직렬화(Serialization, 객체 -> JSON)와 역직렬화(Deserialization, JSON -> 객체)시 필드명을 변경하기 위해 사용된다.
- Snake_case, CamelCase간 변환이 필요할때
- 데이터를 특정 형식으로 변형하는 경우 사용한다.
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserResponseDto {
private Long userId;
private String email;
private String name;
@JsonProperty("tokenIssuedAt")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS") // "T" 포함한 ISO 8601 형식
private LocalDateTime tokenIssuedAt;
}
결과적으로 데이터를 직렬화/역직렬화하여 정상적으로 가져올 수 있었다.
requestTokenIssuedAt: 2025-03-07T13:22:12Z
userTokenIssuedAt: 2025-03-07T13:22:12Z
'Spring' 카테고리의 다른 글
[Lombok] @Builder, DTO 접근제한자 적절한 설정 방법 (0) | 2025.03.11 |
---|---|
[Lombok] @Builder를 클래스가 아닌 메서드에 적용하는 이유 (0) | 2025.03.11 |
[Github] Yml파일 서브모듈에서 불러와서 안전하게 사용하기 (0) | 2025.02.17 |
[Restful API] Restful한 API 문서 작성법, REST API 성숙도 모델 (0) | 2025.02.13 |
[Spring / Swagger] Cors , Fail to Fetch 해결방법 (0) | 2025.01.24 |