[Spring Boot] Spring Data JPA 적용하기

윤동환·2023년 2월 6일
0

Spring Data JPA 적용하기

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

도메인 패키지 생성

domain이란 SW에대한 요구사항 혹은 문제영역이라 생각하시면 됩니다.
즉, 게시클, 댓글, 회원, 정산, 결제 등 다양한 이슈를 담을 패키지 입니다.

DDD(Domain Driven Develop) 방식으로 개발할 예정

게시글 도메인을 담당할 Posts 패키지 및 클래스 생성

Posts
실제 DB Table과 매칭될 class (Entity Class)

package orm.example.springboot.domain.posts;

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

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

// Getter와 NoArgsConstructor는 Lombok의 어노테이션으로서 필수 어노테이션이 아니므로 위에 빼두었습니다. 
// Entity는  JPA의 어노테이션으로 주요 어노테이션은 class에 가깝게 배치하여 차후 관리에 용이하도록 하였습니다.
@Getter // 6
@NoArgsConstructor // 5 
@Entity // 1
public class Posts {
    @Id // 2
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 3
    private Long id;
    @Column(length = 500, nullable = false) // 4 
    private String title;
    @Column(columnDefinition = "TEXT", nullable = false)
    private String content;
    private String author;

    @Builder // 7
    public Posts(String title, String content, String author) {
        this.title = title;
        this.content = content;
        this.author = author;
    }
}
  1. @Entity
    • 테이블과 링크될 클래스임을 나타냅니다.
    • 기본값으로 클래스의 카멜케이스 이름을 언더스코어 네이밍(_)으로 테이블 이름을 매칭합니다.
    ex) SalesManager.java > sales_manager table

  2. @Id
    • 해당 테이블의 PK 필드를 나타냅니다.

  3. @GeneratedValue
    • PK의 생성 규칙을 나타냅니다.
    •스프링 부트 2.0 에서는 GenerationType.IDENTITY 옵션을 추가해야만 auto_increment가 됩니다.
    스프링 부트 2.0 버전과 1.5 버전의 차이

  4. @Column
    • 테이블의 칼럼을 나타내며 굳이 선언하지 않더라도 해당 클래스의 필드는 모두 칼럼이 됩니다.
    • 사용하는 이유는, 기본값 외에 추가로 변경이 필요한 옵션이 있으면 사용합니다.
    • 문자열의 경우 VARCHAR(255)가 기본값인데, 사이즈를 500으로 늘리고 싶거나(ex: title), 타입을 TEXT로 변경하고 싶거나(ex: content) 등의 경우에 사용됩니다.

웬만하면 Entity의 PK는 Long 타입의 Auto_increment를 추천
주민등록번호, 복합키 등은 유니크 키로 별도로 추가하시는 것을 추천

  1. @NoArgsConstructor
    •기본 생성자 자동 추가. public Posts(){}와 같은효과

  2. @Getter
    • 클래스 내 모든 필드의 Getter 메소드를 자동생성

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

    생성자와 빌더의 차이점
    생성 시점에 값을 채워주는 역할은 같으나 빌더의 경우 생성자와 다르게 값을 넣을 때 패턴에 맞게 명확하게 넣어줄 수 있다.
    생성자 : new Example(b, a)
    빌더 : Example.builder().a(a).b(b).build();

Entity Class에 Setter가 없는 이유!!!
Java Bean 규약을 생각하면서 getter/setter를 무작정 생성하는 경우가 있습 니다. 이렇게 되면 해당 클래스의 인스턴스 값들이 언제 어디서 변해야 하는지 코드상으로 명확하게 구분할 수가 없어, 차후 기능 변경 시 정말 복잡해집니다.

잘못 된 예시
public class Order{
      public void setStatus(boolean status){
          this.status = status
      }
}
public void 주문서비스의_취소이벤트 (){ 
	order.setStatus(false); //외부 함수의 기능이 class의 setter로 값을 변경하고 있다.
}
올바른 예시
public class Order{
      public void cancelOrder(){
          this.status = false;
      }
}
public void 주문서비스의_취소이벤트 (){ //외부함수에서 class 멤버 함수를 사용하여 이벤트를 처리하고 있다.
	order.cancelOrder();
}

Setter가 없는데 DB에 어떻게 값을 넣어 insert할수 있을까?
기본 구조 : 생성자를 통해 최종 값을 채운 뒤 DB 삽입
만약 값 변경이 필요시 해당 이벤트에 맞는 public 메소드를 호출하겨 변경

JpaRepository 생성

Posts 클래스로 Database 접근하게 하기 위함

MyBatis등에선 Dao라고 불리는 DB Layer 접근자입니다.
JPA에선 Repository라고 부르며 인터페이스로 생성합니다.

단순히 인터페 이스를 생성 후, JpaRepository<Entity 클래스, PK 타입>를 상속하면 기본 적인 CRUD 메소드가 자동으로 생성됩니다.
@Repository를 추가할 필요도 없습니다.

Entity Class와 Repository는 함께 관리!
Entity Class는 Repository없이 제대로 동작을 할 수 없기 때문에 추후 프로젝트가 커져 도메인 별로 프로젝트를 분리해야할때 함께 움직이도록 도메인 패키지에서 함께 관리하는 것이 좋습니다.

Spring Data JPA 테스트

package orm.example.springboot.domain.posts;

import org.junit.After;
import org.junit.Test;
//스프링 부트 테스트와 JUnit 사이에 연결자 역할
import org.junit.runner.RunWith;
//스프링이 관리하는 빈(Bean)을 주입 받습니다.
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;

@RunWith(SpringRunner.class)
@SpringBootTest //스프링 부트 어플리케이션 테스트 시 테스트에 필요한 거의 모든 의존성을 제공 어노테이션
public class PostsRepositoryTest {
    @Autowired
    PostsRepository postsRepository;

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

    @Test
    public void Posts_save_find() {
    //given
        String title = "1";
        String content = "테스트 본문";
        postsRepository.save(Posts.builder() // 2
                .title(title)
                .content(content)
                .author("jojoldu@gmail.com")
                .build());
        //when
        List<Posts> postsList = postsRepository.findAll(); // 3
        //then
        Posts posts = postsList.get(0);
        assertThat(posts.getTitle()).isEqualTo(title);
        assertThat(posts.getContent()).isEqualTo(content);
    }
}
  1. @After
    • Junit에서 단위 테스트가 끝날 때마다 수행되는 메소드를 지정
    •보통은 배포전 전체테스트를 수행할때 테스트간 데이터침범을 막기위해 사용합
    니다.
    • 여러 테스트가 동시에 수행되면 테스트용 데이터베이스인 H2에 데이터가 그대로 남아있어 다음테스트실행시 테스트가 실패할 수 있습니다.

  2. postsRepository.save
    • 테이블 posts에 insert/update 쿼리를 실행합니다.
    • id 값이 있다면 update가, 없다면 insert 쿼리가 실행됩니다.

  3. postsRepository.findAll
    • 테이블 posts에 있는 모든 데이터를 조회해오는 메소드입니다.

테스트 실행시 에러 발생
스프링 부트와 AWS로 혼자 구현하는 웹 서비스 책의 Test 문으로 실행하니 Test 실패가 되었다.
그 이유는 title이 auto increase되어 저장될 때 숫자 1, 2 .... 로 저장되기 때문이다.
2개의 posts를 저장했을 때 2번째 posts의 test 결과이다.

때문에 기존 title의 값이었던 "타이틀" 을 "1"로 바꾸어 주었다.

실제로 실행된 쿼리 확인하기

쿼리 로그를 ON/OFF할수있는 설정이 있지만 Spring Boot에선 application.properties, application.yml 등의 파일로 한줄의 코드로 설정하는 것을 권장합니다.

application.properties 파일 생성

코드 추가
spring.jpa.show_sql=true

이렇게 Hibernate : 라며 쿼리 실행 내역을 볼 수 있다.

쿼리문을 조금 더 보기 좋게 출력하기

spring.jpa.properties.hibernate.format_sql=true

바인딩 된 값 확인하기

logging.level.org.hibernate.type.descriptor.sql=trace

쿼리 출력 버전 변경

Hibernate로 출력되는 쿼리 로그를 MySQL버전으로 변경할 수 있다.

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect
spring.jpa.properties.hibernate.dialect.storage_engine=innodb
spring.datasource.hikari.jdbc-url=jdbc:h2:mem://localhost/~/testes;MODE=MYSQL

스프링 부트와 AWS로 혼자 구현하는 웹 서비스 책의 코드와 다르게 작성한 이유는 spring boot 버전이 바뀌며 적용해야하는 옵션에 변경이 일어났기 때문인 것으로 보입니다.

profile
모르면 공부하고 알게되면 공유하는 개발자

0개의 댓글