Jam's story

게시판 댓글 저장 , 삭제 기능 본문

Spring

게시판 댓글 저장 , 삭제 기능

애플쩀 2022. 11. 5. 17:45
ArticleCommentController

DTO -> 계층 간 데이터 전송을 위한 객체

 

📌질문 :  여기서 articleCommentRequest를 사용하는이유

 

 articleCommentService.saveArticleComment(articleCommentRequest.toDto(UserAccountDto.of(
                "uno", "pw", "uno@mail.com", null, null
        )));

Entity는 절대 클라이언트에 노출시켜선 안된다.Entity에는 도메인 로직만 구현하고, View와 관련한 로직은 구현하지 않는다. 이기때문에??

 

✔️답변:  해당 컨트롤러의 응답을 독립적으로 작성하고자 했기 때문이다.

이렇게 함으로써 해당 응답을 사용하는 컨트롤러의 응답에 관한 요구사항변경 범위를 ArticleCommentRequest로 어느정도 가둘 수있다.

 

 

📌질문 : Repository 존재이유

Repository는 저장소라는 뜻을 가진 단어인데 , JPA에서 Repository 인터페이스를 생성하면 ,

JpaRepository<Entity, 기본키타입>을 상속받으면 CRUD가 자동으로 생성된다.

즉, JpaRepository를 사용하며, 데이터베이스에 crud의 명령을 실행하게 만드느 인터페이스

간단하게 crud명령을 데이터베이스에 전달하여 원하는 쿼리를 실행할 수 있다.

 

✔️답변: jpa기술을 사용해서 db에 접근하기 위해

 

package com.fastcampus.projectboard.controller;

import com.fastcampus.projectboard.dto.UserAccountDto;
import com.fastcampus.projectboard.dto.request.ArticleCommentRequest;
import com.fastcampus.projectboard.service.ArticleCommentService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@RequiredArgsConstructor
@RequestMapping("/comments")
@Controller
public class ArticleCommentController {

    private final ArticleCommentService articleCommentService;

    @PostMapping ("/new")
    //댓글등록
    public String postNewArticleComment(ArticleCommentRequest articleCommentRequest) {
        // TODO: 인증 정보를 넣어줘야 한다.
        articleCommentService.saveArticleComment(articleCommentRequest.toDto(UserAccountDto.of(
                "uno", "pw", "uno@mail.com", null, null
        )));


        return "redirect:/articles/" + articleCommentRequest.articleId();
    }

    @PostMapping ("/{commentId}/delete")
    public String deleteArticleComment(@PathVariable Long commentId, Long articleId) {
        articleCommentService.deleteArticleComment(commentId);

        return "redirect:/articles/" + articleId;
    }

}
callSuper

callSuper = true: 부모클래스 필드 값도 동일한지 체크.

callSuper = false: 본인클래스 필드 값만 고려.

 

 

 

#44 - 리팩토링: UserAccountDto 에 메타 정보를 넣지 않는 of() 팩토리 메소드 추가
테스트를 용이하게 하기 위해 추가함

테스트 목적 외에도,
repository 레이어에 영속되지 않은 객체는
메타 정보가 없는 것이 당연하므로
새로운 데이터를 만들 때 이 부분을 입력하지 않아도 되는
팩토리 메소드를 만들어주는 것이 의미가 있음

 
FormDataEncoder.java

form data 는 api 와는 다른 형태로 직렬화하여 사용해야 한다.

객체를 규약에 맞게 form data 포맷의 문자열로 바꿔주는 유틸리티를 테스트 전용으로 만들고, 테스트도 함께 작성
이게 없으면 `post().param()` 형태로 값을 넣을 수도 있지만 표현이 실제 post request 전송과 꼭 맞지 않는 듯 하고,
객체가 아니라 파라미터 단위로 넣어줘야 하므로 불편함.

 

비즈니스 로직에서는 필요없기때문에 테스트 전용도구로 등록

test목적으로 넣엇기 떄문에, test쪽에 있고 테스트 컴포넌트로 등록

objectMapper가 빈으로 인식해서 주입을 받을 수 있게끔 만듦

 

object를 받아서 multivalueMap -> 쿼리파라미터 부분만 추출해서 우리가 원하는 결과물을 내게끔 만들었다.

 

package com.fastcampus.projectboard.util;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;

import java.math.BigDecimal;
import java.util.List;

import static org.assertj.core.api.Assertions.*;

@DisplayName("테스트 도구 - Form 데이터 인코더")
@Import({FormDataEncoder.class, ObjectMapper.class})
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = Void.class)
class FormDataEncoderTest {

    private final FormDataEncoder formDataEncoder;

    public FormDataEncoderTest(@Autowired FormDataEncoder formDataEncoder) {
        this.formDataEncoder = formDataEncoder;
    }

    @DisplayName("객체를 넣으면, url encoding 된 form body data 형식의 문자열을 돌려준다.")
    @Test
    void givenObject_whenEncoding_thenReturnsFormEncodedString() {
        // Given
        TestObject obj = new TestObject(
                "This 'is' \"test\" string.",
                List.of("hello", "my", "friend").toString().replace(" ", ""),
                String.join(",", "hello", "my", "friend"),
                null,
                1234,
                3.14,
                false,
                BigDecimal.TEN,
                TestEnum.THREE
        );

        // When
        String result = formDataEncoder.encode(obj);

        // Then
        assertThat(result).isEqualTo(
                "str=This%20'is'%20%22test%22%20string." +
                        "&listStr1=%5Bhello,my,friend%5D" +
                        "&listStr2=hello,my,friend" +
                        "&nullStr" +
                        "&number=1234" +
                        "&floatingNumber=3.14" +
                        "&bool=false" +
                        "&bigDecimal=10" +
                        "&testEnum=THREE"
        );
    }

    record TestObject(
            String str,
            String listStr1,
            String listStr2,
            String nullStr,
            Integer number,
            Double floatingNumber,
            Boolean bool,
            BigDecimal bigDecimal,
            TestEnum testEnum
    ) {}

    enum TestEnum {
        ONE, TWO, THREE
    }

}

 

이 둘은 연관된 dependency

implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
testImplementation 'org.springframework.security:spring-security-test'

 

WebMvcTest는 컨트롤러만 테스트를 한다.  (슬라이스 테스트 ) 일부 꼭 필요한 컨트롤러와 빈들만 스캔

게다가 articleController만 스캔하도록 되어있기 때문에

다른 필요한 것들을 import로 꼭 넣어준다.

@Import({SecurityConfig.class, FormDataEncoder.class})
@WebMvcTest(ArticleController.class)
class ArticleControllerTest {

 

csrf

테스트나 각종 데모에서 발생할 수 있는 문제점을 우회화도록

 

데이터가 변경되는 작업에서는 csrf가 잇어야 테스트가 제대로 통과한다.

 

@RequiredArgsConstructor

롬복어노테이션

 

final이나 @NotNull이 붙은 필드의 생성자를 자동으로 생성해주어

새로운 필드를 추가할때 생성자를 새로 만들 필요가 없다.

 

@RequestMapping(value="" , method= )

요청이 들어왔을 때, 어떤 컨트롤러가 호출되어야하는 지 알려주는 지표

 

@PostMapping

@RequestMapping(value="" , method= )는 메소드 방식을 같이 적어줘야하지만

 PostMapping이나 GetMapping은 값만 적어주기때문에 더 간결하다

 

@RequestMapping에서 value값을 안적어주면 GET 방식과 POST 방식을 모두 처리해준다. 

하나의 url로 get과 post를 같이 처리해주고 싶다면  PostMapping이나 GetMapping를 같이 적어주면된다.

 

->댓글 구현 06:00

@PostMapping쓴 이유

현재  form은 get과 post만 다루게 되어있다. -> ( 폼태그에서 딜리트나 풋이 들어가야 할 상황은 없다. )

 

 

shouldHaveNoInteractions()

아무일도 하지않았다 .

 

 

Comments