[spring-boot] Spring Data JPA

shelly·2020년 9월 11일
0

Spring

목록 보기
6/6

JPA란?

객체 지향 프로그래밍

컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 여러 개의 독립된 단위, 즉 "객체"들의 모임으로 파악하고자 하는 것이다. 각각의 객체는 메시지를 주고받고, 기능과 속성을 한 곳에서 관리하는 기술이다.

관계형 데이터 베이스

데이터의 종속성을 관계(relationship)로 표현하는 것이 관계형 데이터베이스의 특징이다. 즉, 어떻게 데이터를 저장할지에 초점을 맞춘 기술이다.

객체 지향 프로그래밍관계형 데이터 베이스는 양 쪽의 이점을 모두 취하여 사용하기 쉽지 않다. 반복되는 코드를 줄이고 코드를 캡슐화 하는 객체 지향 프로그래밍을 하고 있지만, DB에 접근할때 똑같은 코드가 무수히 반복되는 sql문을 사용해야하기 때문이다.

한 예로, 내가 개발한 Node server 이다. 이를 개발하면서 "비슷한 내용의 SQL을 반복적으로 쓰고 있는 상황에 이렇게 하는 것이 효율적인 코드라고 할 수 있는가"에 대한 의구심을 마음 한 켠에 품고 있었다. 당연히, 효율적이지 못하다..

JPA

위의 딜레마를 해결하기 위해 만들어진 기술이 JPA이다. 개발자는 객체지향적으로 프로그래밍을 하고, JPA가 이를 관계형 데이터베이스에 맞게 SQL을 대신해서 실행한다. 때문에 SQL에 종속적인 개발로부터 자유로워진다!

JPA 실습

의존성 주입

build.gradle에 아래의 두 의존성을 주입하자.

dependencies {
    ...

    compile('org.springframework.boot:spring-boot-starter-data-jpa')
    compile('com.h2database:h2')

    ...
}
  • spring-boot-starter-data-jpa
    스프링 부트용 Spirng Data Jpa 추상화 라이브러리이다.
  • h2
    인메모리 관계형 데이터베이스이다.
    메모리에서 실행되기 때문에 애플리케이션을 재시작할 때마다 초기화된다는 점을 이용하여 테스트 용도로 많이 사용된다.

클래스 생성

[src] > [main] > [java] > [{GroupId}] package에 domain package를 생성하자. 앞으로 도메인을 담을 패키지이다. 도메인이란 게시글, 댓글, 회원, 정산, 결제 등 소프트웨어에 대한 요구사항 혹은 문제 영역이다.
... 아직은 무슨 말인지 모르겠지만, 일단 그러려니 하고 넘어간다.

domain package 안에 posts package를 생성해주고, post package 안에 Posts 클래스를 생성하자.

다음은 Posts 클래스에 작성할 코드이다.

package com.book.springboot.domain.posts;

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

import javax.persistence.*;


@Getter
@NoArgsConstructor
@Entity
public class Posts {
    @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;
    }
}
  • Entity
    JPA 어노테이션으로, 테이블과 링크될 클래스임을 나타낸다.
    클래스의 카멜케이스 이름을 언더스코어 네이밍으로 테이블 이름을 매칭한다.
  • Id
    해당 테이블의 PK 필드를 나타낸다.
  • @GeneratedValue
    PK의 생성 규칙을 나타낸다. GenerationType.IDENTITY 옵션을 추가해야만 auto_increment 옵션이 설정된다.
  • @Column
    테이블의 칼럼을 나타내며, 기본값 외에 추가로 변경이 필요한 옵션이 있으면 사용한다.
  • @NoArgsConstructor
    기본 생성자를 자동으로 추가한다.
    public Posts(){}와 같은 효과이다.
  • @Builder
    해당 클래스의 빌더 패턴 클래스를 생성한다.
    생성자 상단에 선언 시 생성자에 포함된 필드만 빌더에 포함한다.

주의 사항

Getter와 Setter는 하나의 쌍처럼 붙어다닌다. 때문에 예전 Java를 공부할 때, 변수를 하나 선언하면 그에 대한 getter와 setter를 먼저 선언하고 시작했다.

하지만, Entity 클래스에서는 절대 Setter 메소드를 만들지 않는다고 한다. 그 이유는 단순히 함수명을 setValue()로 지정한다면 어떤 기능을 하는지 정확하게 알 수 없어서 차후 기능 변경 시 많은 불편함을 초래하기 때문이다. 따라서 set 함수를 선언할 때는 반드시 의미있는 함수 명으로 선언해야한다.

기본적으로는 1. 생성자를 통해 값을 채우고, 이후 2. 값 변경이 필요한 경우 해당 이벤트에 맞는 이벤트를 호출하여 변경한다.


Jpa Repository 생성

Jpa Repository는 Posts 클래스로 Database를 접근하게 한다.
Post 클래스와 같은 폴더에 PostsRepository 인터페이스를 생성한 후 아래와 같이 입력하자.

package com.book.springboot.domain.posts;

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

public interface PostsRepository extends JpaRepository<Posts, Long> {
}
  • Entity클래스기본 Entity Repository는 항상 같은 폴더에 있어야 한다.
  • JpaRepository<Entity 클래스, PK 타입> 을 상속하면 기본적인 CRUD 메소드가 자동으로 생성된다.

JPA 테스트

[src] > [test] > [java] > [{GroupId}] package 안에 domain package를 생성한 후, domain package 안에 posts package, 그 안에 PostsRepositoryTest Class를 생성해주자.

PostsRepositoryTest의 내용은 다음과 같다

package com.book.springboot.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.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("suhyunified@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에서 단위 테스트가 끝날 때마다 수행되는 메소드를 지정한다.
    여러 테스트가 동시에 수행되면 테스트용 데이터베이스인 H2에 데이터가 그대로 남게되어 다음 테스트 실행 시 테스트가 실패할 수 있다.
  • postsRepository.save
    테이블 posts에 insert/update 쿼리를 실행한다.
    id 값이 있다면 update, 없다면 insert 쿼리가 실행된다.
  • postsRepository.findAll
    테이블 posts에 있는 모든 데이터를 조회해오는 메소드이다.

이제, 테스트를 실행해보면 성공적으로 테스트를 통과한 것을 확인할 수 있다!


아직은 여전히 너무나도 낯설고 어렵다.. 언제 익숙해질까 싶지만, 여러번 만들다보면 어느새 익숙해져있지 않을까 하는 기대를 해본다! 😊

0개의 댓글