[MySQL] Soft Delete를 활용한 Data 삭제 시 Timezone 불일치 해결
발생한 오류
포스트 및 댓글 삭제 시 DB에서 완전히 제거되는 것이 아닌 deleteAt에 삭제 시간을 기록하여 삭제된 것처럼 보이는 Soft Delete 방식을 이용하였다.
Talend API Tester를 이용한 Test를 하던 중 각 시간 데이터를 담는 변수들에 @JsonFormat 을 이용한 Asia/Seoul timeZone 설정을 해주었음에도 삭제 시 UTC Timezone으로 기록이 되는 것을 확인하였다.
원인으로는 Soft Delete 실행 시 UPDATE post SET deleted_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP WHERE id = ? 쿼리문이 실행되며 내가 설정한 Timezone으로 들어가지지 않는다는 것을 알게 되었다.
해결 시도 1️⃣ - datasource 연결 시 url에 Timezone 설정해주기
server:
port: 8080
servlet:
encoding:
force-response: true
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/healing-sns?serverTimezone=Asia/Seoul&useUniCode=yes&characterEncoding=UTF-8 // ✨
username: root
password: root
jpa:
show-sql: true
database-platform: org.hibernate.dialect.MySQL8Dialect
database: mysql
hibernate.ddl-auto: update
properties:
hibernate:
format_sql: true
# jdbc: // ✨
# time_zone: Asia/Seoul
mvc:
pathmatch:
matching-strategy: ant_path_matcher
jwt:
token:
secret: secret.aa.bb.cc
datasource 연결 url에 ?serverTimezone=Asia/Seoul&useUniCode=yes&characterEncoding=UTF-8 을 뒤에 붙이거나,
datasource url 뒤에 ?&useLegacyDatetimeCode=false 을 붙이고 spring.jpa.properties.hibernate.jdbc.time_zone: Asia/Seoul 을 추가 설정 해주는 방법들을 실행해보았지만 여전히 UTC Timezone으로 결과가 나왔다.
해결 시도 2️⃣ - MySQL Server Timezone을 직접 변경하기
현재 MySQL Schema의 time_zone이 어떻게 되어 있는지 확인하는 과정을 거쳤다.
select now(), @@global.time_zone, @@session.time_zone;
모두 시스템 시간을 Timezone으로 사용하고 있음을 확인할 수 있었다.
session Timezone 변경해보기
session Timezone만을 Asia/Seoul로 변경해주고 시스템 시간의 Timezone이 무엇인지 함께 출력해보았다.
결과는 여전히 UTC Timezone이 적용된 시간으로 출력이 되었다.
global Timezone 변경해보기
global Timezone 역시 변경한 후 다시 변경 결과를 확인해보았다.
IntelliJ를 재시작하지 않고 테스트를 진행했을 때는 적용이 되지 않았지만, 재시작 후에 테스트를 진행한 결과 Timezone이 바뀐 것을 확인할 수 있었다.
해결 시도 3️⃣ - DATETIME 타입의 현재 시간을 나타내는 NOW()를 이용한 UPDATE 쿼리 날리기
시간 정보를 담는 데이터들은 모두 DATETIME 타입이다. 그렇다면 UPDATE 쿼리문을 CURRENT_TIMESTAMP 가 아닌 NOW() 로 설정해주면 되지 않을까?
이전에 변경했던 MySQL Timezone을 모두 다시 SYSTEM으로 돌려놓고 @SQLDelete() 내의 쿼리문을 변경하여 테스트를 해보았다.
package com.likelion.healing.domain.entity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "post")
@Getter
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Where(clause = "deleted_at IS NULL")
@SQLDelete(sql = "UPDATE post SET deleted_at = now(), updated_at = now() WHERE id = ?")
@Schema(description = "포스트")
public class PostEntity extends BaseEntity{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Schema(description = "포스트 번호")
private Integer id;
@Column(nullable = false)
@Schema(description = "포스트 제목")
private String title;
@Column(nullable = false, length = 300)
@Schema(description = "포스트 내용")
private String body;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
@Schema(description = "작성자 정보")
private UserEntity user;
@OneToMany(mappedBy = "post", cascade = CascadeType.REMOVE)
private List<CommentEntity> comments = new ArrayList<>();
@Builder
public PostEntity(Integer id, String title, String body, UserEntity user) {
this.id = id;
this.title = title;
this.body = body;
this.user = user;
}
public void updatePost(String title, String body) {
this.title = title;
this.body = body;
}
}
package com.likelion.healing.domain.entity;
import lombok.*;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;
import javax.persistence.*;
@Entity
@Table(name ="comment")
@Getter
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Where(clause = "deleted_at IS NULL")
@SQLDelete(sql = "UPDATE comment SET deleted_at = now(), updated_at = now() WHERE id = ?")
public class CommentEntity extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column
private String comment;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "post_id")
@Setter
private PostEntity post;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
@Setter
private UserEntity user;
@Builder
public CommentEntity(Integer id, String comment, PostEntity post, UserEntity user) {
this.id = id;
this.comment = comment;
this.post = post;
this.user = user;
}
public void setPostAndComment(PostEntity post) {
this.post = post;
post.getComments().add(this);
}
public void updateComment(String comment, UserEntity user) {
this.comment = comment;
this.user = user;
}
}
실행 결과 Timezone이 UTC로 적용된 것을 확인할 수있었다.
이러한 과정을 거쳐 MySQL의 기본 Timezone은 왜 UTC일까에 대한 의문을 갖게 되었고 현재까지 추측으로는 MySQL 서버인 EC2 서버의 호스트 Timezone이 UTC+0이기 때문일 것이라 생각하고 있다.