본문 바로가기
스파르타(부트캠프)/Troubleshooting

[내일배움캠프] 일정관리 앱(develop)에 대한 트러블슈팅

by Kimuky 2024. 11. 15.

https://github.com/kimuky/schedule-management-develop

 

GitHub - kimuky/schedule-management-develop: 자바 숙련주차 (JPA, 인증/인가)

자바 숙련주차 (JPA, 인증/인가). Contribute to kimuky/schedule-management-develop development by creating an account on GitHub.

github.com

 

1.  복합키로 PK를 가능..?

 

1) 개요

- LV 7 단계를 해야하는데 댓글이라는 테이블을 추가해 CRUD를 구현해야한다.

하지만 댓글은 일정에 의존적이며, 유저에 의존적이다. (이 말이 맞는지는 모르겠다.)

 

- 결론적으로는 유저와 일정의 PK를 가져와서 댓글의 FK로 써야한다.

- 그렇다면 Entity를 만드는 방법은 Comment Entity에서 일정, 유저의 각 PK를 @ManytoOne으로 연결해주면 된다.

 

하지만 레포지토리에선 하나의 PK 기준으로 <Schedule, Long>, <User, Long> 요런 형태로 넣어줫는데 키가 두 개인 경우는 어떻게...? 

 

2) 문제 상황

 - 그러면 Entity를 살펴보자

package com.example.schedulemanagementdevelop.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Getter
@Entity
@Table(name = "comment")
public class Comment extends BaseEntity {

    @Id
    @Setter
    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;

    @Id
    @Setter
    @ManyToOne
    @JoinColumn(name = "schedule_id")
    private Schedule schedule;

    private String contents;

    public Comment() {

    }

    public Comment(String contents) {
        this.contents = contents;
    }

    public void updateContents(String contents) {
        this.contents = contents;
    }

}

음.. 오류는 뜨지않지만 경고가 뜬다.. 아직 여기에 대해서 이해를 하지 못한 것 같다.

도저히 이렇게 셋팅하고는 CommentRepository를 어떻게 구성해야하는지 모르겠다.

 

3) 해결

 - 당장 구현을 위해서 댓글 자체적으로 PK(Auto_increment)를 통해 구성을 바꿔주었다.

package com.example.schedulemanagementdevelop.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Getter
@Entity
@Table(name = "comment")
public class Comment extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

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

    @Setter
    @ManyToOne
    @JoinColumn(name = "schedule_id")
    private Schedule schedule;

    private String contents;

    public Comment() {

    }

    public Comment(String contents) {
        this.contents = contents;
    }

    public void updateContents(String contents) {
        this.contents = contents;
    }

}

이렇게 하면 CommentRepository를 걱정하지 않아도 된다.

package com.example.schedulemanagementdevelop.repository;


import com.example.schedulemanagementdevelop.entity.Comment;
import com.example.schedulemanagementdevelop.entity.Schedule;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface CommentRepository extends JpaRepository<Comment, Long> {
    List<Comment> findByScheduleId(Long findSchedule);
}

 

 

4) 결론

- 아직 JPA를 이해하지 못한 것 같다..

- 복잡한 구조를 가지면 엔터티를 어떻게 구성을 해야할지 감이 오지 않는다. 

- @EmbeddedId를 이용해 복합키를 매칭할 수 있다는데 이해가 가지않는다. (추가적으로 공부하기)


2.  싱글 필드? Jackson? 변환? Delegating -> 오류 발생

1) 개요

 - 일정에 달리는 댓글을 구현하는 중에 Dto, Controller, Service, Repository를 다 구성하고 디버깅을 해보았는데 오류가 발생했다.

 

2) 문제 상황

- 컴파일 단에서 오류가 발생되지 않는다.

- Postman으로 댓글 생성 시, Bad Request

- 음 .. 컨트롤러에서 오류가 발생하는 것 같다.

 

2024-11-15T00:18:00.820+09:00 WARN 21736 --- [schedule-management-develop] [nio-8080-exec-9] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of `com.example.schedulemanagementdevelop.dto.CommentRequestDto` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)]

 

- CommentRequestDto 쪽에 문제가 발생한 것 같다.

- Json Parser 오류, 비록 생성자가 있더라도 객체로부터 역직렬화 할 수 없다..? 이게 무슨 소리인가..

- delegate?, property-based Creator?

@Getter
public class CommentRequestDto {

    @NotBlank
    private final String contents;

    public CommentRequestDto(String contents) {
        this.contents = contents;
    }
}

- 생성자도 잘 있고 문제도 없어보인다.

 

그럼 컨트롤러에서 문제가??

public class CommentController {

    private final CommentService commentService;

    @PostMapping("/{scheduleId}/comment")
    public ResponseEntity<CommentResponseDto> saveComment(
    	@PathVariable Long scheduleId, 
        @Valid @RequestBody CommentRequestDto requestDto, 
        HttpServletRequest servletRequest) {

        HttpSession session = servletRequest.getSession(false);
        String userEmail = (String) session.getAttribute("email");

        CommentResponseDto commentResponseDto = commentService.saveComment(scheduleId, requestDto, userEmail);

        return new ResponseEntity<>(commentResponseDto, HttpStatus.CREATED);
    }
    
}

- 문제가 없어보인다. @Requestbody를 통해 정상적으로 dto랑 맵핑이 되어야한다.. (Json으로 데이터만 잘 보낸다면)

 

뭔가 오류메시지를 보니 dto 쪽 문제가 확실한 것 같아서 dto를 수정해보았다.

@Getter
public class CommentRequestDto {

    @NotBlank
    private String contents;

	public CommentRequestDto() {
    }
    
    public CommentRequestDto(String contents) {
        this.contents = contents;
    }
}

음 작동이 된다.. 물론 이게 맞는 구조인가 생각해보면 아닌 것 같다..

 

- GPT한테 물어봐도 무슨 말인지 통 모르겠다.

 

 

3) 해결

- 해결 방법

 1. 필드를 하나 더 추가한다. -> 작동 O

 2. final을 제거한다. -> 작동 O

 

1번 방식처럼 한다면 불필요한 필드를 추가해줘야하는데 그렇게 하기는 싫었다..

2번 방식처럼 한다면 final을 제거해야하는게 과연 이게 맞는가를 생각해보면 리퀘스트가 바뀌는 것도 아닌데 이상했다.

 

- 구글링을 통해 찾아보니 필드가 하나이면  Delgating vs Properties 방식 중 뭘 써야 할지 몰라서 발생하는 에러라고 한다..

(무슨 소리인지 1도 모르겠다..)

 

- 그래서 해결 방법을 찾아보았더니 생성자에 @JsonCreator를 붙혀주면 된다..

- 다른 방법은 @ConstructorProperties 어노테이션을 생성자에 넣어주면 된다고는 하나 해보지는 않았다.

package com.example.schedulemanagementdevelop.dto.comment;

import com.fasterxml.jackson.annotation.JsonCreator;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;

@Getter
public class CommentRequestDto {

    @NotBlank
    private final String contents;
    
    @JsonCreator
    public CommentRequestDto(String contents) {
        this.contents = contents;
    }
}

 

그 뒤, 결과

 

 

잘 된다..

 

 

4) 결론

- Json 변환 과정을 제대로 이해하지 못했다.

- Json에서 싱글필드를 쓸려면 delgating, properties 방식을 이해하자