Backend/MySQL

[MySQL] Soft Delete를 활용한 Data 삭제 시 Timezone 불일치 해결

th42500 2023. 1. 8. 21:31

발생한 오류

포스트 및 댓글 삭제 시 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이기 때문일 것이라 생각하고 있다.