[스프링부트와 AWS로 혼자 구현하는 웹 서비스] 프로젝트에 Spring Data JPA 적용하고 테스트하기

세이라·2023년 7월 22일
0

스터디를 통해 스프링부트와 AWS로 혼자 구현하는 웹 서비스(저자 이동욱) 서적을 공부하는 중입니다.

공부/실습한 내용을 정리한 포스팅입니다.
책에 모르는 부분이 있으면 구글링하거나 챗gpt에 물어봐서 보충하였습니다.
(아직 초보라 모르는 부분이 많아 이것저것 다 적었습니다.)

참고한 사이트 출처는 포스팅 맨 하단에 적었습니다.

Spring Data JPA 적용

과정

  1. domain 패키지 생성

domain : 게시글, 댓글, 회원, 정산, 결제 등 소프트웨어에 대한 요구사항 혹은 문제영역.
※ 기존의 MyBatis와 같은 SQL Mapper를 사용할 시 dao 패키지를 썼지만, domain 패키지와는 다름. xml에 쿼리를 담고, 클래스는 오로지 쿼리의 결과만 담지 않음.

  1. domain 패키지에 posts 패키지 생성 후 Posts 클래스 생성
  2. Posts 클래스에 아래와 같이 작성
package com.webservice.springboot.springboot_board.domain.posts;

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

import javax.persistence.*;

@Getter
@NoArgsConstructor
@Entity
public class Posts extends BaseTimeEntity{
    @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 Posts(String title, String content, String author){
        this.title=title;
        this.content=content;
        this.author=author;
    }

    public void update(String title,String content){
        this.title = title;
        this.content = content;
    }
}
  1. posts 패키지에 PostsRepository 인터페이스 생성
  2. PostsRespository 인터페이스에 아래의 코드 작성
package com.webservice.springboot.springboot_board.domain.posts;

import org.springframework.data.jpa.repository.JpaRepository;

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

설명

Posts 클래스

Annotation 순서

  • 주요 annotation을 클래스에 가깝게 둠.
    - 롬복은 코드를 단순화시켜 주지만 필수 annotation이 아님. 그러므로 annotation 중에서 가장 위로 둠으로써 클래스에서 가장 멀게 둠.
    → 코틀린 등의 새 언어 전환으로 롬복이 더이상 필요 없을 시 쉽게 삭제 가능.

@NoArgsConstructor

  • 기본 생성자 추가
  • 기본 생성자를 추가해주는 이유 : JPA 공식 문서에 Entity 클래스의 요구사항에 '반드시 매개변수가 없는 public 혹은 protected의 생성자를 가져야 한다'고 적혀 있음. JPA는 Entity 객체를 인스턴스화하고 필드에 값을 채워넣기 위해 Reflection API를 사용해 런타임 시점에 동적으로 기본생성자를 통해 클래스를 인스턴스화하여 값을 매핑하기 때문. 이때, Entity 클래스에 기본 생성자가 존재하지 않으면 동적으로 인스턴스화 할 수 없기 때문 반드시 기본 생성자 필요.

※ Reflection API : 클래스의 정보를 얻고 다룰 수 있는 강력한 기능 제공

Entity 클래스

  • 실제 DB의 테이블과 매칭될 클래스
  • 실제 쿼리를 안 날리고, Entity 클래스 수정을 통해 작업.
@Entity
  • 테이블과 링크될 클래스임을 나타냄.
  • 기본값으로 클래스의 카멜케이스 이름을 언더스코어 네이밍으로 테이블 이름 매칭.

@Id

  • 해당 테이블의 PK 필드를 나타냄.

@GeneratedValue

  • PK의 생성 규칙
  • strategyGenerationType.IDENTITY로 둬야 auto_increment가 됨.
PK를 auto_increment로 생성하는 이유

웬만하면 Entity의 PK는 Long 타입의 Auto_increment 추천(MYSQL 기준 bigint). 주민등록번호와 같이 비즈니스상 유니크 키나 여러 키를 조합한 복합키로 PK로 잡을 경우 아래와 같은 상황 발생.

  • FK를 맺을 시, 다른 테이블에서 복합키 전부를 갖고 있거나, 중간 테이블을 하나 더 둬야 하는 상황이 발생 가능.
  • 인덱스에 좋은 영향을 끼치지 못함.
  • 유니크한 조건이 변경될 경우, PK 전체를 수정해야하는 일 발생.
    ※ 주민등록번호, 복합키 등은 유니크 키로 별도로 추가하는 걸 추천.

@Column

  • 테이블의 칼럼을 나타내며 굳이 선언하지 않아도 해당 클래스의 필드는 모두 칼럼이 되지만, 기본 값 외에 추가로 변경이 필요한 옵션이 있을 시 사용.
    : 문자열의 경우, VARCHAR(255)가 기본값. 사이즈를 늘리고 싶거나 타입을 TEXT로 변경하고 싶을 때 사용.

@Builder

  • 해당클래스의 빌더 패턴 클래스 생성.

※ 빌더 패턴 : 복잡한 객체의 생성 과정과 표현 방법을 분리하여 다양한 구성의 인스턴스를 만드는 생성 패턴. 생성자에 들어갈 매개 변수를 메서드로 하나하나 받아들이고 마지막에 통합 빌드해서 객체를 생성하는 방식.
※ 빌더 패턴의 장점
1. 점층적 생성자 패턴의 단점 해결
: 생성자 오버로딩 열거X
데이터 순서에 상관없이 객체를 만들어낼 수 있어 생성자 인자 순서를 파악할 필요X
2. Java Beans 패턴 문제 해결
: 일관성 문제(setter 메서드 미호출로 인한 객체가 유효하지 않는 문제), 불변성 문제(불변함을 보장하지 못하는 문제) 해결
※ 점층적 생성자 패턴 : 생성자 오버로딩 열거하는 방식.
※ Java Beans 패턴 : 매개변수가 없는 생성자로 객체 생성 후 Setter 메서드를 통해 클래스 필드의 초깃값 설정하는 방식.

  • 생성자 상단에 선언 시 생성자에 포함된 필드만 빌더에 포함.

Entity 클래스에 Setter 메서드를 만들지 않는 이유

  • 해당 클래스의 인스턴스의 값들이 언제 어디서 변해야하는지 코드상으로 명확하게 구분할 수 없음. 대신, 해당 필드의 값 변경이 필요하며 명확히 그 목적과 의도를 나타낼 수 있는 메소드 추가. Setter가 없으므로 빌더 클래스 사용.

PostsRepository 인터페이스

Repository

  • DB Layer 접근자로 인터페이스로 생성 후 JpaRepository<Entity클래스, PK타입> 상속하면 기본적인 CRUD 자동으로 생성

Spring Data JPA 테스트

과정

  1. test 디렉토리에 domain.posts 패키지 생성 후 PostsRepositoryTest 클래스 생성
  2. 아래와 같이 코드 작성
package com.webservice.springboot.springboot_board.domain.posts;

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.test.context.junit4.SpringRunner;

import java.time.LocalDateTime;
import java.util.List;

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

@RunWith(SpringRunner.class)
@SpringBootTest
public class PostsRepositoryTest {
    @Autowired
    PostsRepository postsRepository;

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

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

        postsRepository.save(Posts.builder()
                .title(title)
                .content(content)
                .author("jojoldu@gmail.com")
                .build());
        //when
        List<Posts> postsList = postsRepository.findAll();

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

설명

  • @After : JUnit에서 단위 테스트가 끝날 때마다 수행되는 메소드 지정. 보통 배포 전 전체 테스트 수행 시 테스트간 데이터 침범을 막기 위해 사용.
  • save(Entity) : 테이블에 insert, update 쿼리 실행.(id 있으면 update, 없으면 insert)
  • findAll() : 테이블에 있는 모든 데이터 반환.

H2 설정

application.properties

  • .properties : Java 응용프로그램 내 사용되는 설정을 적는 파일. 이 설정 파일을 외부에서 파일을 넣을 수도 있고, 프로젝트 내부적으로도 넣어서 사용할 수 있음.
  • 스프링부트가 애플리케이션 구성 시 자동으로 로딩.
  • 각 줄은 일반적으로 하나의 프로퍼티 저장하며, key, value 쌍으로 저장됨.
  • 줄의 맨 앞에 # 또는 !을 사용하여 주석처리 가능.

실제로 실행된 쿼리 로그로 보기

  1. src>main>resources에 application.properties 파일 생성
  2. 아래와 같이 코드 추가
spring.jpa.show-sql=true

출력 쿼리 로그 MySQL 버전으로 변경

(H2는 MySQL 쿼리를 수행해도 정상적으로 작동)

  1. application.properties에 아래의 코드 추가
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
  1. id bigint generated by default as identity에서 id bigint not null auto_increment로 변경됨

H2 DB 직접 접근

메모리에서 실행하기 때문 직접 접근하기 위해선 웹 콘솔을 사용
1. application.properties에 아래의 코드 추가 - 웹 콘솔 옵션 활성화

spring.h2.console.enabled=true
  1. 'http://localhost:8080/h2-console' 접속
  2. JDBC:URL이 'jdbc:h2:mem:testdb'로 되어있는지 확인 후 되어있지 않으면 작성 후 Connect 버튼 클릭

출처

Entity Class에 Lombok @NoArgsConstructor(access=PROTECTED)를 붙이는 이유
💠 빌더(Builder) 패턴 - 완벽 마스터하기
springboot 개발 환경에 따른 properties 사용 방법 (local, dev, prod)

1개의 댓글

comment-user-thumbnail
2023년 7월 22일

이번에는 Spring Data JPA를 적용하는 글을 읽었습니다. 글에서는 각 코드 라인이 어떤 역할을 하는지, 어떤 원리로 작동하는지에 대해 상세하게 설명하고 있어서 이해가 잘 됐습니다. Repository, Entity 클래스, 그리고 테스트 과정에 대한 부분이 특히 인상적이었습니다. 이런 유익한 글을 써주셔서 감사합니다. 앞으로도 좋은 글 기대할게요!

답글 달기