728x90

이전글

 

[05] 리팩토링 - 2

이전글 [05] 리팩토링 - 1 이전글 [Project] 06. SNS 웹 서비스 제작 이전글 [Project] 05. SNS 웹 서비스 제작 이전글 https://chordplaylist.tistory.com/219 첫 번째 미션 AWS EC2에 Docker 배포 Gitlab CI & Crontab CD Swagger 회

chordplaylist.tistory.com

댓글 Entity

package com.likelion.finalproject.domain.entity;

import com.likelion.finalproject.domain.dto.comment.CommentReadResponse;
import lombok.*;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;

import javax.persistence.*;
import java.time.LocalDateTime;

import static javax.persistence.FetchType.LAZY;

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Builder
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@Entity
public class Comment extends BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private String comment;

    @JoinColumn(name = "user_id")
    @ManyToOne(fetch = LAZY)
    private User user;

    @JoinColumn(name = "post_id")
    @ManyToOne(fetch = LAZY)
    private Post post;

    private LocalDateTime deletedAt;

    public void update(String comment) {
        this.comment = comment;
    }

    public CommentReadResponse toResponse() {
        return CommentReadResponse.builder()
                .id(this.id)
                .comment(this.comment)
                .userName(this.user.getUserName())
                .postId(this.post.getId())
                .createdAt(this.getCreatedAt())
                .build();
    }
}

댓글 작성

  • 앤드포인트
    POST /posts/{postsId}/comments
  • 입력
{
"comment" : "comment test4"
}
  • 리턴
{
	"resultCode": "SUCCESS",
	"result":{
		"id": 4,
		"comment": "comment test4",
		"userName": "test",
		"postId": 2,
		"createdAt": "2022-12-20T16:15:04.270741"
	}
}

Comment Requst Dto

@AllArgsConstructor
@NoArgsConstructor
@Getter
public class CommentRequest {
    private String comment;

    public Comment toEntity(Integer id, User user, Post post) {
        System.out.println("toentity = " + post.getCreatedAt());
        return Comment.builder()
                .id(id)
                .comment(this.comment)
                .user(user)
                .post(post)
                .build();
    }
}

Comment Write Response

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Builder
public class CommentWriteResponse {

    private Integer id;
    private String comment;
    private String userName;
    private Integer postId;
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm:ss", timezone = "Asia/Seoul")
    private LocalDateTime createdAt;

    public static CommentWriteResponse of(Comment savedComment) {
        return CommentWriteResponse.builder().
                id(savedComment.getId())
                .comment(savedComment.getComment())
                .userName(savedComment.getUser().getUserName())
                .postId(savedComment.getPost().getId())
                .createdAt(savedComment.getCreatedAt())
                .build();
    }
}

테스트 코드 작성

class CommentControllerTest {
    @Test
    @DisplayName("댓글 작성 성공")
    void success_write_comment() throws Exception {
        User user = UserFixture.get("chordpli", "1234");
        Post post = PostFixture.get(user);
        CommentRequest request = new CommentRequest("댓글 작성");
        CommentWriteResponse response = CommentWriteResponse.builder()
                .id(1)
                .comment(request.getComment())
                .userName(user.getUserName())
                .postId(post.getId())
                .createdAt(LocalDateTime.now())
                .build();

        given(commentService.writeComment(any(), any(), any())).willReturn(response);

        String url = String.format("/api/v1/posts/%d/comments", post.getId());


        mockMvc.perform(post(url).with(csrf())
                        .header(HttpHeaders.AUTHORIZATION, "Bearer " + token)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsBytes(request)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.resultCode").exists())
                .andExpect(jsonPath("$.resultCode").value("SUCCESS"))
                .andExpect(jsonPath("$.result.id").exists())
                .andExpect(jsonPath("$.result.id").value(1))
                .andExpect(jsonPath("$.result.comment").exists())
                .andExpect(jsonPath("$.result.comment").value("댓글 작성"))
                .andExpect(jsonPath("$.result.postId").exists())
                .andExpect(jsonPath("$.result.postId").value(1))
                .andExpect(jsonPath("$.result.createdAt").exists())
                .andDo(print());
    }
}

api 구현

Controller

...
public class CommentController {
    ...
    private final CommentService commentService;

    @ApiOperation(value = "댓글 작성")
    @PostMapping("/{postId}/comments")
    public Response<CommentWriteResponse> writeComment(@PathVariable Integer postId,
                                                       @RequestBody CommentRequest request,
                                                       Authentication authentication){
        String userName = authentication.getName();
        CommentWriteResponse response = commentService.writeComment(postId, request, userName);
        return Response.success(response);
    }
}
  1. String 문자열을 담은 Comment Request Dto를 사용하여 Comment를 추가한다.

Service

public class CommentService {

    private final CommentRepository commentRepository;
    private final Services service;

    public CommentWriteResponse writeComment(Integer postId, CommentRequest requset, String userName) {
        User user = service.validateGetUserByUserName(userName);
        Post post = service.validateGetPostById(postId);
        Comment savedComment = Comment.builder()
                .user(user)
                .post(post)
                .comment(requset.getComment())
                .build();
        commentRepository.save(savedComment);

        return CommentWriteResponse.of(savedComment);
    }
}
  1. 해당 유저가 존재하는지 확인
  2. 댓글을 작성할 해당 게시글이 존재하는지 확인
  3. 둘 다 존재한다면 request를 받아서 댓글을 저장한다.

댓글 수정

  • 앤드포인트
    PUT /posts/{postId}/comments/{id}
  • 입력
{
	"comment" : "modify comment"
}
  • 리턴
{
	"resultCode": "SUCCESS",
	"result":{
		"id": 4,
		"comment": "modify comment",
		"userName": "test",
		"postId": 2,
		"createdAt": "2022-12-20T16:15:04.270741",
		"lastModifiedAt": "2022-12-23T16:15:04.270741"
	}
}

Comment Modify Response

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Builder
public class CommentModifyResponse {

    private Integer id;
    private String comment;
    private String userName;
    private Integer postId;
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm:ss", timezone = "Asia/Seoul")
    private LocalDateTime createdAt;
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm:ss", timezone = "Asia/Seoul")
    private LocalDateTime lastModifiedAt;

    public static CommentModifyResponse of(Comment comment) {
        return CommentModifyResponse.builder()
                .id(comment.getId())
                .comment(comment.getComment())
                .userName(comment.getUser().getUserName())
                .postId(comment.getPost().getId())
                .createdAt(comment.getCreatedAt())
                .lastModifiedAt(comment.getLastModifiedAt())
                .build();
    }
}

테스트 코드 작성

class CommentControllerTest {
    @Test
    @DisplayName("댓글 수정 성공")
    void success_modify_comment() throws Exception {
        User user = UserFixture.get("chordpli", "1234");
        Post post = PostFixture.get(user);
        Comment comment = CommentFixture.get(user, post);
        CommentRequest request = new CommentRequest("댓글 수정쓰");
        CommentModifyResponse response = CommentModifyResponse.builder()
                .id(1)
                .comment(request.getComment())
                .userName(user.getUserName())
                .postId(post.getId())
                .createdAt(LocalDateTime.now())
                .lastModifiedAt(LocalDateTime.now())
                .build();

        given(commentService.modifyComment(any(), any(), any(), any())).willReturn(response);

        String url = String.format("/api/v1/posts/%d/comments/%d", post.getId(), comment.getId());


        mockMvc.perform(post(url).with(csrf())
                        .header(HttpHeaders.AUTHORIZATION, "Bearer " + token)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsBytes(request)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.resultCode").exists())
                .andExpect(jsonPath("$.resultCode").value("SUCCESS"))
                .andExpect(jsonPath("$.result.id").exists())
                .andExpect(jsonPath("$.result.id").value(1))
                .andExpect(jsonPath("$.result.comment").exists())
                .andExpect(jsonPath("$.result.comment").value("댓글 수정쓰"))
                .andExpect(jsonPath("$.result.postId").exists())
                .andExpect(jsonPath("$.result.postId").value(1))
                .andExpect(jsonPath("$.result.id").exists())
                .andExpect(jsonPath("$.result.id").value(1))
                .andExpect(jsonPath("$.result.createdAt").exists())
                .andExpect(jsonPath("$.result.lastModifiedAt").exists())
                .andDo(print());
    }

api 구현

Controller

...
public class CommentController {
    ...
    @ApiOperation(value = "댓글 수정")
    @PutMapping("/{postId}/comments/{id}")
    public Response<CommentModifyResponse> modifyComment(@PathVariable Integer postId,
                                                       @PathVariable Integer id,
                                                       @RequestBody CommentRequest request,
                                                       Authentication authentication){
        String userName = authentication.getName();
        CommentModifyResponse response = commentService.modifyComment(postId, id, request, userName);
        return Response.success(response);
    }
}

Service

...
public class CommentService {
    ...
    public CommentModifyResponse modifyComment(Integer postId, Integer id, CommentRequest request, String userName) {
        User user = service.validateGetUserByUserName(userName);
        Post post = service.validateGetPostById(postId);
        Comment comment = service.validateGetCommentById(id);
        service.validateMatchUsers(user, comment);

        comment.update(request.getComment());

        return CommentModifyResponse.of(commentRepository.save(comment));
    }
}
  1. 해당 유저가 존재하는지 확인
  2. 댓글을 작성할 해당 게시글이 존재하는지 확인
  3. 수정할 댓글이 존재하는지 확인
  4. 댓글의 작성자와 수정 요청하는 요청자가 같은 사람인지 확인
  5. set기능을 하는 update 메서드를 생성하여 Entity의 값을 수정한 후 DB에 저장한다.

문제 발생

빌더 패턴을 사용하여 수정 기능을 구현하는 도중, Comment 엔티티가 상속한 BaseEntity의 CreatedAt이 Null로 반환되는 현상이 발견되었다.
문제는 Database에는 CreatedAt이 무사히 값이 존재하고 있다는 것이었다.
이것이 왜 문제냐면 Database에 Null로 입력된다면 해당 변수에 어노테이션으로 @Column(updatable = false)를 추가하면 매우 간단하게 해결되는 문제이기 때문이다.
하지만 Database에는 이상 없이 CreateAt의 값이 존재하고 있었다.

문제 해결을 위한 시도

  1. Builder 패턴의 문제인가 싶어서 save 후 다시 DB에서 값을 꺼내왔지만 여전히 null로 받아오는 중.

이유 추측

  1. Dto를 Entity로 변경하는 과정에서 기존의 정보는 넘어가고 있으나 @Column(updatable = false)이므로 createAt에 대한 정보는 update가 되지 않음. 그래서 null의 정보를 객체가 갖게 된다.
  2. save 후 다시 findById를 통해 정보를 다시 받아와도 왜 null인가? 이미 영속성에 null로 된 Entity가 존재하므로 DB가 아닌 영속성에서 정보를 받아오기 때문에 null을 그대로 가져오는 것 같다.

그렇다면?

  1. flush를 해주고 find하는 방법
  2. set 메서드를 사용하여 Entity 추가 생성 없이 내용을 수정하는 방법

선택

Set 관련 메서드를 하나 만들었다. 메서드 명에 Set이 적혀있으면 수정에 대해 오해가 생길 수 있으므로 명확한 메서드 명을 제작하였다.

...
public class Comment extends BaseEntity{
    ...
    public void update(String comment) {
        this.comment = comment;
    }
}
반응형

'프로젝트 > Archive' 카테고리의 다른 글

[07] Like Api 개발  (0) 2023.01.05
[06] Comment Api 개발 - 2  (0) 2023.01.05
[05] 리팩토링 - 2  (0) 2022.12.29
[05] 리팩토링 - 1  (0) 2022.12.29
[04] Post Test 코드 작성 - 2 (Service Test)  (0) 2022.12.27
코드플리