본문 바로가기
스프링 부트와 AWS

03장. 스프링 부트에서 JPA로 데이터베이스 다뤄보자03

by danny-j 2022. 11. 14.

등록 기능 만들기

  • PostsApiController를 web 패키지에
  • PostsSaveRequestDto를 web.dto 패키지에
  • PostsService를 service.posts 패키지에

 

PostsApiController

package com.danny.makewebalone.web;

import com.danny.makewebalone.service.posts.PostsService;
import com.danny.makewebalone.web.dto.PostsSaveRequestDto;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

@RequiredArgsConstructor
@RestController
public class PostsApiController {

    private final PostsService postsService;

    @PostMapping("/api/v1/posts")
    public Long save(@RequestBody PostsSaveRequestDto requestDto) {
        return postsService.save(requestDto);
    }
}

 

PostsService

package com.danny.makewebalone.service.posts;

import com.danny.makewebalone.web.domain.posts.PostsRepository;
import com.danny.makewebalone.web.dto.PostsSaveRequestDto;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@RequiredArgsConstructor
@Service
public class PostsService {
    private final PostsRepository postsRepository;

    @Transactional
    public Long save(PostsSaveRequestDto requestDto) {
        return postsRepository.save(requestDto.toEntity()).getId();
    }
}

 

Controller와 Service에 @Autowired가 없다?

스프링에서 Bean을 주입받는 방식

  • @Autowired
  • setter
  • 생성자

이중 가장 권장하는 방식은 생성자로 주입받는 방식

@Autowired는 권장하지 않음

생성자로 Bean 객체를 받도록 하면 @Autowired와 동일한 효괄를 볼 수 있다.

 

그렇다면 생성자는 어디에?

@RequiredArgsConstructor에서 해결

final이 선언된 모든 필드를 인자값으로 하는 생성자

생성자를 직접 안 쓰고 롬복 어노테이션을 사용한 이유는 해당 클래스의 의존성 관계가 변경될 때마다 생성자 코드를 계속해서 수정하는 번거로움을 해결하기 위함

 

PostsSaveRequestDto

package com.danny.makewebalone.web.dto;

import com.danny.makewebalone.web.domain.posts.Posts;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class PostsSaveRequestDto {
    private String title;
    private String content;
    private String author;

    @Builder
    public PostsSaveRequestDto(String title, String content, String author) {
        this.title = title;
        this.content = content;
        this.author = author;
    }

    public Posts toEntity() {
        return Posts.builder()
                .title(title)
                .content(content)
                .author(author)
                .build();
    }
}

Entity 클래스와 거의 유사한 형태임에도 Dto를 추가로 생성한 이유

  • Entity 클래스는 Request/Response 클래스로 사용하면 안됨
  • Entity 클래스는 데이터베이스와 맞닿은 핵심 클래스
  • Entity 클래스를 기준으로 테이블이 생성되고, 스키마가 변경됨
  • 화면 변경은 아주 사소한 변경인데 이를 위해 테이블과 연결된 Entity 클래스를 변경하는 것은 너무 큰 변경
  • 수많은 서비스 클래스나 비즈니스 로직들이 Entity 클래스를 기준으로 동작
  • Request와 Response용 Dto는 View를 위한 클래스라 정말 자주 변경이 필요
  • Controller에서 결과값으로 여러 테이블을 조인해서 줘야 할 경우가 빈번하므로 Entity클래스만으로 표현하기가 어려운 경우가 많으니 꼭 Entity클래스와 Controller에서 쓸 Dto는 분리해서 사용할 것

 

PostsApiControllerTest 생성하기

package com.danny.makewebalone.web;

import com.danny.makewebalone.web.domain.posts.Posts;
import com.danny.makewebalone.web.domain.posts.PostsRepository;
import com.danny.makewebalone.web.dto.PostsSaveRequestDto;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.http.*;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

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

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class PostsApiControllerTest {

    @LocalServerPort
    private int port;

    @Autowired
    private TestRestTemplate restTemplate;

    @Autowired
    private PostsRepository postsRepository;

    private MockMvc mvc;

    @After
    public void tearDown() throws Exception {
        postsRepository.deleteAll();
    }

    @Test
    public void Posts_등록된다() throws Exception {
        //given
        String title = "title";
        String content = "content";
        PostsSaveRequestDto requestDto = PostsSaveRequestDto.builder()
                .title(title)
                .content(content)
                .author("author")
                .build();

        String url = "http://localhost:" + port + "/api/v1/posts";

        //when
        ResponseEntity<Long> responseEntity = restTemplate.postForEntity(url, requestDto, Long.class);

        //then
        assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(responseEntity.getBody()).isGreaterThan(0L);

        List<Posts> all = postsRepository.findAll();
        assertThat(all.get(0).getTitle()).isEqualTo(title);
        assertThat(all.get(0).getContent()).isEqualTo(content);
    }
}

Api Controller를 테스트하는데 HelloController와 달리 @WebMvcTest를 사용하지 않는 이유

  • @WebMvcTest의 경우 JPA 기능이 작동하지 않기 때문
  • Controller와 ControllerAdvice 등 외부 연동과 관련된 부분만 활성화 됨
  • 지금과 같이 JPA 기능까지 한번에 테스트할 때는 @SpringBootTest와 TestRestTemplate 사용

 

Test 결과

Passed

댓글