BOOK 2 - 스프링부트와 AWS로 혼자 구현하는 웹 서비스(2)

스프링부트와 AWS로 혼자 구현하는 웹 서비스 - 2

JPA에 관한 기본적인 이론

JPA (Java Persistence API)

image

  • JAVA ORM 기술에 대한 표준 명세 => 즉, 자바에서 제공하는 표준 API

  • Spring의 PSA (Portable Service Abstract)에 의해서 관계형 데이터베이스와 객체 지향 프로그래밍의 패러다임 불일치를 해소하기 위해 ORM을 사용하도록 만든 인터페이스

    • 관계형 데이터베이스는 어떻게 데이터를 저장할지에 초점이 맞춰진 기술이고, 객체 지향 프로그래밍은 기능과 속성을 한 곳에서 관리하는 기술이다.

    • 즉, 관계형 데이터베이스에서는 객체 지향 프로그래밍의 추상화, 캡슐화, 다형성, 상속 등의 다양한 객체 모델링을 구현할 수 없다.

    • 이러한 패러다임 불일치를 해결하기 위해 개발자는 객체 지향적으로 프로그래밍을 하고, JPA가 이를 관계형 데이터베이스에 맞게 SQL을 대신 생성해 실행하도록 한다.

      => 개발자는 더이상 SQL에 종속적인 개발을 하지 않아도 된다.

  • JAVA application에서 관계형 데이터베이스를 사용하는 방식을 정의한 인터페이스이다.

    • JPA는 말그대로 인터페이스이기 때문에 라이브러리처럼 특정 기능을 제공하지는 않는다.
    • JPA 인터페이스를 구현한 대표적인 오픈 소스 : Hibernate , EclipseLink , DataNucleus

ORM vs SQL Mapper

  • ORM (Object - Relation Mapping)
    • 객체와 DB 테이블을 매핑시킨다.
    • 즉, 객체간 관계를 바탕으로 SQL을 자동으로 생성해주어 SQL 쿼리가 아니라 메서드로 데이터를 조작할 수 있다.
  • SQL Mapper
    • 객체와 SQL을 매핑시킨다.
    • SQL문으로 직접 데이터를 조작한다.
    • Mybatis , ibatis

JDBC

  • DB에 접근할 수 있도록 JAVA에서 제공하는 API

  • 모든 JAVA Database access 기술의 근간으로 ORM이나 SQL Mapper도 내부적으로 JDBC API를 사용하고 있다.

Spring Data JPA

  • Hibernate 같은 JPA 구현체들을 좀 더 쉽게 사용하고자 추상화시킨 Spring에서 제공하는 모듈

  • Hibernate를 바로 사용하지 않고 Spring Data JPA를 사용하는 이유

    • 구현체 교체의 용이성

      • Hibernate 외의 다른 구현체로 쉽게 교체할 수 있다.
    • 저장소 교체의 용이성

      • 관계형 데이터베이스 외의 다른 저장소로 쉽게 교체할 수 있다.

프로젝트에 Spring Data JPA 적용

Spring Data JPA 의존성 등록

  • build.gradle 파일에 의존성 추가

    dependencies {
    	...
    	compile("org.springframework.boot:spring-boot-starter-data-jpa:${springBootVersion}")
    	compile('com.h2database:h2')
    }
    
    • spring-boot-starter-data-jpa

      • 스프링 부트용 Spring Data Jpa 추상화 라이브러리

      • 스프링 부트 버전에 맞춰 자동으로 JPA 관련 라이브러리들의 버전을 관리해준다.

    • h2

      • 인메모리 DB
      • 별도의 설치가 필요 없이 프로젝트 의존성만으로 관리할 수 있다.
      • 메모리에서 실행되기 때문에 어플리케이션을 재시작할 때마다 초기화된다는 점을 이용하여 테스트 용도로 많이 사용한다.
      • JPA 테스트, 로컬 환경에서의 구동에 사용하기 위해 추가하였다.

Entity 클래스 생성

  • Review 클래스 생성

    @Getter
    @NoArgsConstructor
    @Entity
    public class Review {
      
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
      
        @Column(length = 500, nullable = false)
        private String title;
      
        @Column(columnDefinition = "TEXT", nullable = false)
        private String content;
      
        @Column(nullable = false)
        private Long memberId;
      
        @Builder
        public Review(String title, String content, Long memberId) {
            this.title = title;
            this.content = content;
            this.memberId = memberId;
        }
    }
    
    • @Entity

      • 관계형 데이터베이스의 테이블과 링크될 클래스임을 나타낸다.
      • 테이블 명을 지정해주지 않으면 기본값으로 카멜케이스 이름을 언더스코어 네이밍으로 테이블 이름을 매칭한다.
        • ex) ReviewPost.java -> review_post table
    • @Id : 해당 테이블의 PK 필드

    • @GeneratedValue

      • PK의 생성 규칙을 지정
      • GenerationType.IDENTITY : auto_increment 옵션
    • @Column

      • 테이블의 column을 지정

        • 굳이 선언하지 않더라도 해당 클래스의 필드는 모두 column이다.

        • 기본값 외에 추가로 변경이 필요한 옵션이 있으면 사용한다.

      • String type의 경우 VARCHAR(255) 가 기본값인데 사이즈를 늘리고 싶은 경우 length 옵션을 추가하면 된다.

    • @Builder

      • 해당 클래스의 Builder pattern 클래스를 생성
      • 클래스 상단에 선언할 경우 클래스의 모든 필드를 포함한 Builder pattern이 생성된다.
      • 생성자 상단에 선언할 경우 생성자에 포함된 필드만 포함한 Builder pattern이 생성된다.

Repository 클래스 생성

  • Repository 클래스는 DB Layer 접근자이다.

  • Interface로 생성 후 JpaRepository<Entity 클래스 타입, PK 타입> 를 상속( implements )하면 기본적인 CRUD 메서드가 자동으로 생성된다.

    public interface ReviewRepository extends JpaRepository<Review, Long> {
      
    }
    
  • Entity 클래스는 기본 Repository 없이 제대로 역할을 할 수 없기 때문에 같은 domain 패키지에서 함께 관리해야 한다.

ReviewRepository 테스트 코드 작성

  • ReviewRepository 를 이용하여 데이터를 저장( save )하고 전체 데이터를 조회( findAll )하는 기능을 테스트

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class ReviewRepositoryTest {
      
        @Autowired
        ReviewRepository reviewRepository;
      
        @After
        public void cleanUp() {
            reviewRepository.deleteAll();
        }
      
        @Test
        public void save_and_get_first_review() {
            // given
            reviewRepository.save(Review.builder()
            .title("title")
            .content("content")
            .memberId(1L)
            .build());
      
            // when
            List<Review> reviewList = reviewRepository.findAll();
      
            // then
            Review review = reviewList.get(0);
            assertThat(review.getTitle(), is("title"));
            assertThat(review.getContent(), is("content"));
            assertThat(review.getMemberId(), is(1L));
        }
    }
    
    • @After
      • Junit에서 단위 테스트가 끝날 때마다 수행되는 메서드를 지정
      • 일반적으로 배포 전 전체 테스트를 수행할 때 테스트간 데이터 침범을 막기 위해 사용한다.
    • reviewRepository.save
      • review 테이블에 insert/update 쿼리를 실행한다.
        • id 값이 테이블에 있다면 update , 없다면 insert 쿼리를 실행한다.
    • reviewRepository.findAll : review 테이블에 있는 모든 데이터 조회
  • 별다른 설정 없이 @SpringBootTest 를 사용할 경우 H2 DB를 자동으로 실행해 준다.

Query Log를 위한 설정

  • 실제로 실행된 쿼리를 로그로 확인할 수 있도록 하는 옵션을 줄 수 있다.

  • src/main/resources 밑에 프로젝트 설정을 위한 application.properties 파일 생성

    spring.jpa.show-sql=true
    
    • show-sql : 쿼리 로그 설정을 ON
  • ReviewRepositotyTest 의 단위 테스트 실행 시 콘솔에 다음과 같이 쿼리 로그가 나타나는 것을 확인할 수 있다.

    image