[Spring&AWS][3-2] 프로젝트에 Spring Data JPA를 적용해보자

kiteB·2022년 3월 14일
0

Spring-AWS

목록 보기
3/13
post-thumbnail

이 글은 책 「스프링 부트와 AWS로 혼자 구현하는 웹 서비스」를 공부하고 정리한 글입니다.

이번 시간에는 게시판 프로젝트에 Spring Data JPA를 적용해보자!


[ 프로젝트에 Spring Data JPA 적용하기 ]

✅ 의존성 추가하기

Spring Data JPA를 사용하기 위해서는 build.gradle에 spring-data-jpah2에 대한 의존성을 추가해야 한다.

runtimeOnly 'com.h2database:h2'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
  • spring-boot-starter-data-jpa
    • 스프링 부트용 Spring Data Jpa 추상화 라이브러리
    • 스프링 부트 버전에 맞춰 자동으로 JPA 관련 라이브러리들의 버전을 관리해준다.
  • h2
    • 인메모리 관계형 데이터베이스이다.
    • 별도의 설치가 필요 없이 프로젝트 의존성만으로 관리할 수 있다.
    • 메모리에서 실행되기 때문에 애플리케이션을 재시작할 때마다 초기화된다는 점을 이용하여 테스트 용도로 많이 사용된다.
    • 이 책에서는 JPA의 테스트, 로컬 환경에서의 구동에서 사용할 예정이다.

의존성 추가를 완료했다면 본격적으로 JPA 기능을 사용해보자!

1. Post 클래스

먼저 도메인을 담을 패키지domain 패키지를 만든 뒤, posts 패키지와
Post 클래스를 만든다.

💡 도메인이란
게시글, 댓글, 회원, 정산, 결제 등 소프트웨어에 대한 요구사항 혹은 문제 영역을 말한다.

@Getter
@NoArgsConstructor
@Entity
public class Post {

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

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

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

    private String author;

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

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

✅ 어노테이션 분석

  • @Entity
    • 테이블과 링크될 클래스임을 나타낸다.
    • 기본값으로 클래스의 카멜케이스 이름을 언더스코어 네이밍(_)으로 테이블 이름을 매칭한다.
    • Ex) SalesManager.javasales_manager table
  • @Id
    • 해당 테이블의 PK 필드를 나타낸다.
  • @GeneratedValue
    • PK의 생성 규칙을 나타낸다.
    • 스프링 부트 2.0에서는 GenerationType.IDENTITY 옵션을 추가해야만 auto_increment가 된다.
  • @Column
    • 테이블의 칼럼을 나타내며 굳이 선언하지 않더라도 해당 클래스의 필드는 모두 칼럼이 된다.
    • 그럼에도 불구하고 사용하는 이유는, 기본값 외에 변경이 필요한 옵션이 있으면 사용한다.
    • 문자열의 경우 VARCHAR(255)가 기본값인데,
      • 사이즈를 500으로 늘리고 싶거나 (ex: title),
      • 타입을 TEXT로 변경하고 싶거나 (ex: content) 등의 경우에 사용된다.
  • @NoArgsConstuctor
    • 기본 생성자를 자동으로 추가한다.
    • public Posts() {}와 같은 효과
  • @Getter
    • 클래스 내 모든 필드의 Getter 메소드를 자동으로 생성한다.
  • @Builder
    • 해당 클래스의 빌더 패턴 클래스를 생성한다.
    • 생성자 상단에 선언 시 생성자에 포함된 빌드만 빌더에 포함한다.

서비스 초기 구축 단계에서는 테이블 설계(여기에서는 Entity 설계)가 빈번하게 변경되는데,
이때 롬복의 어노테이션들은 코드 변경량을 최소화시켜 주기 때문에 적극적으로 사용한다.

📌 참고 | Entity의 PK

  • 웬만하면 Entity의 PK는 Long 타입의 Auto_increment를 추천한다.
    (MySQL 기준으로 이렇게 하면 bigint 타입이 된다.)
  • 주민등록번호와 같이 비즈니스상 유니크 키나, 여러 키를 조합한 복합키로 PK를 잡을 경우 테이블을 하나 더 둬야하거나 PK 전체를 수정해야 하는 등 여러모로 불편하다.
    주민등록번호, 복합키 등은 유니크 키로 별도로 추가하는 것이 좋다.

✅ 어노테이션 순서

이 책에서는 주요 어노테이션을 클래스에 가깝게 둔다. 어노테이션을 정렬하는 기준은 다음과 같다.

  • @Entity는 JPA의 어노테이션이며,
    @Getter@NoArgsConstuctor는 롬복의 어노테이션이다.
  • 롬복은 코드를 단순화시켜주지만 필수 어노테이션이 아니므로
    • 주요 어노테이션인 @Entity를 클래스에 가깝게 두고,
    • 롬복 어노테이션을 그 위로 두었다.
  • 이렇게 하면 이후에 코틀린 등의 새 언어 전환으로 롬복이 더이상 필요 없을 경우 쉽게 삭제할 수 있다.

✅ Setter가 없는 이유

위의 코드를 잘 살펴보면 Post 클래스에는 Setter 메소드가 없다는 것을 확인할 수 있다.

자바빈 규약을 생각하면서 getter/setter를 무작정 생성하는 경우가 있는데, 이렇게 되면 해당 클래스의 인스턴스 값들이 언제 어디서 변해야 하는지 코드상으로 명확하게 구분할 수가 없어, 차후 기능 변경 시 복잡해진다.

그래서 Entity 클래스에는 절대 Setter 메소드를 만들지 않는다!

그렇다면 Setter가 없는 상황에서 값을 어떻게 채워서 DB에 삽입해야 할까?

기본적인 구조는 생성자를 통해 최종값을 채운 후 DB에 삽입하는 것이다.

만약 값 변경이 필요한 경우 해당 이벤트에 맞는 public 메소드를 호출하여 변경한다. 책에서는 생성자 대신 @Builder를 통해 제공되는 빌더 클래스를 사용한다.

  • 생성자나 빌더나 생성 시점에 값을 채워주는 역할은 똑같지만
  • 생성자의 경우 지금 채워야 할 필드가 무엇인지 명확하게 지정할 수 없다.

✅ 생성자 vs 빌더

Example(b, a)와 같이 a와 b의 위치를 변경해도 코드를 실행하기 전까지는 문제를 발견할 수가 없다.

public Example(String a, String b) {
   this.a = a;
   this.b = b;
}

하지만 빌더를 사용하게 되면 다음과 같이 어느 필드에 어떤 값을 채워야할지 명확하게 인지할 수 있다.

Example.builder()
    .a(a)
    .b(b)
    .build();

앞으로 모든 예제는 이렇게 빌더 패턴을 적극적으로 사용하니, 잘 익혀두는 것이 좋다!


2. JpaRepository 생성

이제 Post 클래스로 Database를 접근하게 해줄 JpaRepository를 생성해보자.

public interface PostRepository extends JpaRepository<Post, Long> {
}

인터페이스 생성 후 JpaRepository<Entity클래스, PK타입>를 상속하면, 기본적인 CRUD 메소드가 자동으로 생성된다.

Entity는 기본 Repository 없이는 제대로 역할을 하지 못할 만큼 Entity 클래스와 기본 Entity Repository는 아주 밀접한 관계를 갖는다. 그러므로 Entity 클래스와 기본 Entity Repository는 도메인 패키지에서 함께 관리해야 한다.


[ Spring Data Jpa 테스트 코드 작성하기 ]

@RunWith(SpringRunner.class)
@SpringBootTest
public class PostRepositoryTest {

    @Autowired
    PostRepository postRepository;

    @After
    public void cleanup() {
        postRepository.deleteAll();
    }

    @Test
    public void 게시글저장_불러오기() {
        //given
        String title = "테스트 게시글";
        String content = "테스트 본문";

        postRepository.save(Post.builder()
                .title(title)
                .content(content)
                .author("mmy789@naver.com")
                .build());

        //when
        List<Post> postList = postRepository.findAll();

        //then
        Post posts = postList.get(0);
        assertThat(posts.getTitle()).isEqualTo(title);
        assertThat(posts.getContent()).isEqualTo(content);
    }
}

✅ 코드 분석

  • @After
    • Junit에서 단위 테스트가 끝날 때마다 수행되는 메소드를 지정
    • 배포 전 전체 테스트를 수행할 때 테스트간 데이터 침범을 막기 위해 사용한다.
    • 여러 테스트가 동시에 수행되면 테스트용 데이터베이스인 H2에 데이터가 그대로 남아 있어 다음 테스트 실행 시 테스트가 실패할 수 있다.
  • postRepository.save
    • 테이블 post에 insert/update 쿼리를 실행한다.
    • id 값이 있다면 update, 없다면 insert 쿼리가 실행된다.
  • postRepository.findAll
    • 테이블 post에 있는 모든 데이터를 조회해오는 메소드이다.

별다른 설정 없이 @SpringBootTest를 사용할 경우 H2 데이터베이스를 자동으로 실행해준다.

✅ 테스트 실행 결과


다음 시간에는 본격적으로 API를 만들어보자!

profile
🚧 https://coji.tistory.com/ 🏠

0개의 댓글