Showing

[스프링부트] Spring Data JPA 적용(2) JpaRepository 생성 본문

JAVA, SPRING/스프링 부트와 AWS로 혼자 구현하는 웹서비스

[스프링부트] Spring Data JPA 적용(2) JpaRepository 생성

RabbitCode 2023. 6. 2. 23:19

*이동욱 저, 스프링부트와 aws로 혼자 구현하는 웹서비스를 학습하면서 작성한 포스팅입니다.

1.  JpaRepository 생성

앞서 Posts 클래스 생성이 끝났으므로, Posts 클래스로 Database를 접근하게 해 줄 JpaRepository를 생성한다. 

JpaRepository DAO 데이터베이스(DB) 레이어에 접근하기 위한 접근자이다. JPA에선 Repository라고 부르며 인터페이스로 생성한다. 단순히 인터페이스를 생성 후, JpaRepository<Entity 클래스, PK 타입>을 상속하면 기본적인 CRUD 메소드가 자동으로 생성된다. @Repository를 추가할 필요도 없다. 다만 주의할 점은 Entity 클래스와 기본 Entity Repository는 함께 위치해야 한다는 점이다. 둘은 아주 밀접하고,Entity 클래스는 기본 Repository 없이는 제대로 역할을 할 수 없다.

 나중에 프로젝트 규모가 커져 도메인 별로 프로젝트를 분리해야 하면 이때 Entity 클래스와 기본 Repository는 함께 움직여야 하므로 도메인 패키지에서 함께 관리한다. 

 

package com.jojo.book.springbootwebservice.domain.posts;
import org.springframework.data.jpa.repository.JpaRepository;

public interface PostsRepository extends JpaRepository<Posts, Long>{

}

(1) JpaRepository VS  DAO

  1. JpaRepository: JpaRepository 스프링 프레임워크에서 제공하는 인터페이스다. 인터페이스는 Java Persistence API(JPA) 스펙을 구현한 객체 지향적인 데이터베이스 접근 기술인 Hibernate 기반으로 한다. JpaRepository JPA EntityManager 사용하여 데이터베이스에 접근하고, CRUD(Create, Read, Update, Delete) 작업을 수행할 있는 다양한 메서드를 제공한다. 또한 쿼리 메서드를 사용하여 데이터베이스 쿼리를 쉽게 작성할 있으며, 페이징, 정렬, 필터링 등의 기능도 내장되어 있다. JpaRepository 인터페이스를 상속받은 인터페이스를 정의하고, 스프링이 자동으로 구현체를 생성하여 사용할 있다. JpaRepository 스프링 데이터 JPA 일부로 제공되며, 개발자가 데이터베이스 접근 계층을 구현하는 사용된다.
  2. DAO (Data Access Object): DAO는 일반적으로 자바 엔터프라이즈 애플리케이션에서 사용되는 데이터베이스 접근 패턴. 데이터베이스 접근 로직을 캡슐화하고, 비즈니스 로직과 데이터베이스 간의 결합도를 낮추는 데 중점. DAO는 일반적으로 인터페이스로 정의되며, 데이터베이스에 접근하기 위한 메서드를 포함한다. 구체적인 DAO 클래스는 해당 인터페이스를 구현하여 실제 데이터베이스 쿼리 및 연산을 수행한다. DAO는 주로 JDBC(Java Database Connectivity)나 JdbcTemplate과 같은 로우레벨 데이터베이스 접근 기술과 함께 사용된다. DAO 패턴은 데이터베이스에 대한 접근 코드를 분리하여 유지보수성을 향상시키고, 테스트 가능한 코드 작성을 도와준다. 또한 트랜잭션 관리, 예외 처리 등과 같은 공통 로직을 처리하기 위한 용도로 사용될 수도 있다.

JpaRepository와 DAO의 주요 차이점

<기술 스택>

  • JpaRepository: JpaRepository는 스프링 데이터 JPA의 일부로서, JPA를 기반으로 한 객체 지향적인 데이터베이스 접근 기술. Hibernate와 같은 JPA 구현체를 사용하여 데이터베이스에 접근
  • DAO: DAO는 보통 JDBC나 JdbcTemplate과 같은 로우레벨 데이터베이스 접근 기술과 함께 사용되는 데이터베이스 접근 패턴

<목적>

  • JpaRepository: JpaRepository는 JPA 스펙을 구현한 객체 지향적인 데이터베이스 접근 기술로, 객체와 관계형 데이터베이스 간의 매핑과 CRUD 작업을 처리하는 데 중점. 주로 스프링 애플리케이션에서 데이터베이스 접근 계층을 구현하는 데 사용.
  • DAO: DAO는 데이터베이스 접근 로직을 캡슐화하고, 비즈니스 로직과 데이터베이스 간의 결합도를 낮추는 데 중점. 주로 자바 엔터프라이즈 애플리케이션에서 사용되며, 로우레벨의 데이터베이스 접근 기술과 함께 사용.

요약하자면, JpaRepository는 스프링 데이터 JPA를 기반으로 한 객체 지향적인 데이터베이스 접근 기술이고, DAO는 로우레벨 데이터베이스 접근 기술과 함께 사용되는 데이터베이스 접근 패턴


2.  Spring Data JPA 테스트 코드 작성

test 디렉토리에 domain.posts 패키지를 생성하고, 테스트 클래스는 PostsRepositoryTest란 이름으로 생성한다.

package com.jojo.book.springbootwebservice.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("jojo@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);
    }
}

이 테스트를 실행할 경우 H2가 자동으로 실행된다. 

코드 설명

package com.jojo.book.springbootwebservice.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("jojo@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
    public void cleanup(){
        postsRepository.deleteAll();
    }

    @After
junit에서 단위 테스트가 끝날 때마다 수행되는 메소드를 지정. 보통은 배포 전 전체 테스트를 수행할 때 테스트 간 데이터 침범을 막기 위해 사용한다. 여러 테스트가 동시에 수행되면 테스트용 데이터베이스인 H2에 데이터가 그대로 남아 있어 다음 테스트 실행 시 테스트가 실패할 수 있다.

 

 postsRepository.save

테이블 posts에 insert/update 쿼리를 실행한다. id값이 있다면 update가, 없다면 insert 쿼리가 실행된다. 

 

 postsRepository.findAll

테이블 posts에 있는 모든 데이터를 조회해오는 메소드이다.

3.  실행된 쿼리 로그 살펴보기

스프링 부트에서는 application.properties, application.yml 등의 파일로 한 줄의 코드로 쿼리 로그를 ON/OFF할 수 있다. 이를 지원할 뿐만 아니라 권장하므로 이것을 사용하도록 한다.

src/main/resources 디렉토리 아래에 appplication.properties 파일을 생성한다.

spring.jpa.show-sql=true

drop table부터 create table, insert, select, delete 등을 확인할 수 있다.

Hibernate: drop table posts if exists
Hibernate: create table posts (id bigint generated by default as identity, author varchar(255), content TEXT not null, title varchar(500) not null, primary key (id))
2023-06-02 23:10:13.975  INFO 45656 --- [    Test worker] o.h.t.schema.internal.SchemaCreatorImpl  : HHH000476: Executing import script 'org.hibernate.tool.schema.internal.exec.ScriptSourceInputNonExistentImpl@26d24d7a'
2023-06-02 23:10:13.976  INFO 45656 --- [    Test worker] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2023-06-02 23:10:14.314  INFO 45656 --- [    Test worker] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2023-06-02 23:10:14.338  WARN 45656 --- [    Test worker] aWebConfiguration$JpaWebMvcConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2023-06-02 23:10:14.469  INFO 45656 --- [    Test worker] c.j.b.s.d.posts.PostsRepositoryTest      : Started PostsRepositoryTest in 1.892 seconds (JVM running for 2.372)
Hibernate: insert into posts (id, author, content, title) values (null, ?, ?, ?)
2023-06-02 23:10:14.550  INFO 45656 --- [    Test worker] o.h.h.i.QueryTranslatorFactoryInitiator  : HHH000397: Using ASTQueryTranslatorFactory
Hibernate: select posts0_.id as id1_0_, posts0_.author as author2_0_, posts0_.content as content3_0_, posts0_.title as title4_0_ from posts posts0_
Hibernate: select posts0_.id as id1_0_, posts0_.author as author2_0_, posts0_.content as content3_0_, posts0_.title as title4_0_ from posts posts0_
Hibernate: delete from posts where id=?
2023-06-02 23:10:14.626  INFO 45656 --- [       Thread-4] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'
2023-06-02 23:10:14.627  INFO 45656 --- [       Thread-4] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2023-06-02 23:10:14.627  INFO 45656 --- [       Thread-4] .SchemaDropperImpl$DelayedDropActionImpl : HHH000477: Starting delayed evictData of schema as part of SessionFactory shut-down'
Hibernate: drop table posts if exists

한가지 걸리는 것은 create table 쿼리를 보면 id bigint generated by default as identity라는 옵션으로 생성된다. 이는 H2 쿼리 문법이 적용되었기 때문이다. H2는 MySQL의 쿼리를 수행해도 정상적으로 작동하기 때문에 이후 디버깅을 위해서 출력되는 쿼리 로그를 MySQL 버전으로 변경해보도록 한다. 이 옵션 역시 application.properties에서 설정 가능하다. 다음 코드를 추가한다. 


spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect

spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect

Hibernate: create table posts (id bigint not null auto_increment, author varchar(255), content TEXT not null, title varchar(500) not null, primary key (id)) engine=InnoDB

id bigint not null auto_increment라고 변경된 것을 보니 옵션이 잘 적용되었다!

JPA와 H2에 대한 기본적인 기능과 설정을 진행했으니, 본격적으로 API를 만들어보도록 한다.