스프링 부트 따라하기 - 2. spring boot + JPA로 API 만들어보기

noyo0123·2019년 12월 9일
0

2-1 도메인 코드 만들기

도메인? 웹 서비스에서 쓰이는 객체라고 보시면 될거같아요. 어느 서비스든 회원은 빠지지 않으니 회원을 데이터로 관리하는 코드를 생성한다라고 이해를 했습니다.
제가 따라하는 예제에서는 게시물에 대한 객체를 도메인을 생성하여 일단 따라해보겠습니다.

다음과 같은 경로로 domain 패키지(디렉토리)를 생성해줍니다.

해당 패키지 아래에 Posts 클래스와 PostsRepository 인터페이스를 생성합니다.

java/com/foodpangex1/webservice/domain/Posts.java

package com.foodpangex1.webservice.domain;

import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.*;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Entity
public class Posts {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;

    @Column(length = 500, nullable = false)
    private String title;

    @Column(columnDefinition = "TEXT", nullable = false)
    private String content;

    private String author;

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

참고
@NoArgsConstructor
@NoArgsConstructor 접근 권한을 최소화 합니다.
JPA에서는 프록시를 생성을 위해서 기본 생성자를 반드시 하나를 생성해야합니다. 이때 접근 권한이 protected 이면 됩니다. 굳이 외부에서 생성을 열어둘 필요가 없습니다.

@Builder
@Builder : 해당 클래스의 빌더패턴 클래스를 생성
생성자 상단에 선언시 생성자에 포함된 필드만 빌더에 포함합니다.


다시 돌아와서 Posts 클래스는 실제 DB의 테이블과 매칭될 클래스이며 보통 Entity클래스라고도 합니다.
JPA를 사용하시면 DB 데이터에 작업할 경우 실제 쿼리를 날리기 보다는, 이 Entity 클래스의 수정을 통해 작업합니다.

코드를 복붙해보니까 Import를 일일이 안하고 한 번에 하는걸 찾아보니

Add unambiguous imports on the fly
Optimize imports on the fly
이 둘을 체크해주시고 insert imports on paste를 ALL-> ASK로 바꿨습니다.
뭔지를 물어보고 바꾸는 거라 생각해서 했고요.

import할때 겹치는 경우는 @Id 처럼 여러 패키지가 임포트가 될 수 있는 상황에서는 직접 세팅해줘야 합니다.

여기서 @Id는 테이블의 Pk와 매핑됩니다. 그리고 이 PK에 대한 전략도 있습니다.
JPA 기본 키 매핑 전략,@Id
JPA에서 기본 키 매핑 전략에는 크게 4가지가 있다.

보통은 자동 생성을 해서 사용한다고 합니다.


1)직접 할당 : 기본 키를 애플리케이션에서 직접 엔티티클래스의 @Id 필드에 set해준다.

2)자동 생성 : 대리 키 사용 방식

- IDENTITY : 기본 키 생성을 데이터베이스에 위임한다.(ex MySQL - AUTO INCREMENT...)

- SEQUENCE : 데이터베이스 시퀀스를 사용해서 기본 키를 할당한다.(ex Oracle sequence...)

- TABLE : 키 생성 테이블을 사용한다.(ex 시퀀스용 테이블을 생성해서 테이블의 기본키를 저장하고 관리한다.)

java/com/foodpangex1/webservice/domain/PostsRepository.java

public interface PostsRepository extends JpaRepository<Posts, Long>{
}

보통 ibatis/MyBatis 등에서 Dao라고 불리는 DB Layer 접근자입니다.
JPA에선 Repository라고 부르며 인터페이스로 생성합니다.
단순히 인터페이스를 생성후, JpaRepository<Entity클래스, PK타입>를 상속하면 기본적인 CRUD 메소드가 자동생성 됩니다.
특별히 @Repository를 추가할 필요도 없습니다.

2-2. 테스트 코드 작성하기

java/com/foodpangex1/webservice/domain/PostsRepositoryTest.java

package com.foodpangex1.webservice.domain;

import org.junit.jupiter.api.AfterEach;
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 java.util.List;

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;

@DisplayName("PostsRepositoryTest")
@SpringBootTest
public class PostsRepositoryTest {

    @Autowired
    PostsRepository postsRepository;

    @AfterEach
    public void cleanup() {
        /**
         이후 테스트 코드에 영향을 끼치지 않기 위해
         테스트 메소드가 끝날때 마다 respository 전체 비우는 코드
         **/
        postsRepository.deleteAll();
    }


    @Test
    public void 게시글저장_불러오기() {
        //given
        postsRepository.save(Posts.builder()
                .title("테스트 게시글")
                .content("테스트 본문")
                .build());

        //when
        List<Posts> postsList = postsRepository.findAll();

        //then
        Posts posts = postsList.get(0);
        assertThat(posts.getTitle(), is("테스트 게시글"));
        assertThat(posts.getContent(), is("테스트 본문"));
    }
}

스프링부트 2.0버전부터는 JUnit5를 사용한다네요.
사용하는 방법을 차츰 알아야겠지만 일단은 우선 돌아가는게 중요하니
레퍼런스를 참조 초록불이 들어오게 해봤습니다.
(Junit은 spring-boot-starter-test에 있기 때문에 별도로 build.gradle에 추가하실 필요가 없습니다.)

테스트가 정상적으로 동작했음을 보여줍니다.

Jest를 써봤는데, 약간 느낌이 비슷하네요.

DB가 설치가 안되어있는데 Repository를 사용할 수 있는 이유는, SpringBoot에서의 테스트 코드는 메모리 DB인 H2를 기본적으로 사용하기 때문입니다.
테스트 코드를 실행하는 시점에 H2 DB를 실행시킵니다.
테스트가 끝나면 H2 DB도 같이 종료됩니다.

2-3. Controller & DTO 구현

@Getter
@Setter
@NoArgsConstructor
public class PostsSaveRequestDto {

    private String title;
    private String content;
    private String author;

    public Posts toEntity(){
        return Posts.builder()
                .title(title)
                .content(content)
                .author(author)
                .build();
    }
}
@RestController
@AllArgsConstructor
public class WebRestController {

    private PostsRepository postsRepository;

    @GetMapping("/hello")
    public String hello() {
        return "HelloWorld";
    }

    @PostMapping("/posts")
    public void savePosts(@RequestBody PostsSaveRequestDto dto){
        postsRepository.save(dto.toEntity());
    }
}

보시면 postsRepository 필드에 @Autowired가 없습니다.
스프링프레임워크에선 Bean 을 주입받는 방식들이 아래와 같이 있는데요.

@Autowired
setter
생성자
이중 가장 권장하는 방식이 생성자로 주입받는 방식입니다.
(@Autowired는 비권장방식입니다.)
즉, 생성자로 Bean 객체를 받도록 하면 @Autowired와 동일한 효과를 볼 수 있다는 것입니다.

그러면 위에서 생성자는 어디있을까요?
바로 @AllArgsConstructor 에서 해결해줍니다.
모든 필드를 인자값으로 하는 생성자를 Lombok의 @AllArgsConstructor이 대신 생성해 준 것입니다.
위 코드는 실제로는 아래와 같은 형태입니다.

Controller에서 @RequestBody로 외부에서 데이터를 받는 경우엔 기본생성자 + set메소드를 통해서만 값이 할당됩니다.
그래서 이때만 setter를 허용합니다.


TODO

@AllArgsConstructor
@NoArgsConstructor

@NoArgsConstructor 어노테이션은 파라미터가 없는 기본 생성자를 생성해주고, @AllArgsConstructor 어노테이션은 모든 필드 값을 파라미터로 받는 생성자를 만들어줍니다.

@Builder

@SpringBootTest
@Autowired
@Repository
@Test
@After

Bean을 어떻게 주입받는지 모르겠다...

롬복 레퍼런스 : [자바] 자주 사용되는 Lombok 어노테이션 - Daleseo
롬복 더 자세한 레퍼런스 : 실무에서 Lombok 사용법 - popit
import 자동 레퍼런스 : 인텔리J(IntelliJ) 임포트(Import) / Eclipse Ctrl + Shift + O in IntelliJ IDEA - [우아한 프로그래밍]
JPA 레퍼런스 : JPA - 기본 키 매핑 전략(@Id) - 코드스타트
@RunWith 레퍼런스 : [JUnit] @RunWith, @ContextConfiguration 그리고 @SpringApplicationConfiguration - 미래의 개발왕
JUnit5 레퍼런스 : [JUnit5] SpringBoot 2에 JUnit5 적용 - 버리버리야

profile
자바스크립트를 주언어로 개발을 배우고 있습니다. 서로 아는 것들을 공유해요~

0개의 댓글