본문 바로가기
스프링부트와 AWS로 혼자 구현하는 웹서비스

스프링 부트 Spring Data JPA 적용 및 등록,수정,조회 API 작성하기

by 초이사 2023. 2. 24.

이 글은 '스프링부트와 AWS로 혼자 구현하는 웹 서비스 - 이동욱(jojoldu)'을 공부하며 작성한 글로 생략된 내용은 책을 구매해서 확인하는 것을 권장합니다.

참고 소스코드 깃허브 https://github.com/jojoldu/freelec-springboot2-webservice

http://www.yes24.com/Product/Goods/83849117

 

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

가장 빠르고 쉽게 웹 서비스의 모든 과정을 경험한다. 경험이 실력이 되는 순간!이 책은 제목 그대로 스프링 부트와 AWS로 웹 서비스를 구현한다. JPA와 JUnit 테스트, 그레이들, 머스테치, 스프링

www.yes24.com

 

책에 3장에 해당하는 내용에 대해 구현하면서 정리하고자 한다.

 

JPA

  • SQL에 종속적인 개발을 개선하기 위해 등장
  • 서로 지향하는 바가 다른 객체지향 프로그래밍 언어와 관계형 데이터베이스를 중간에서 패러다임 일치를 시켜주기 위한 기술
  • 개발자는 객체지향적으로 프로그래밍을 하고, JPA가 관계형 데이터베이스에 맞게 SQL을 대신 생성 및 실행한다. -> r객체 중심 개발을 하게 되면서 유지보수도 용이해짐
  • JPA는 인터페이스로서 자바표준명세서이다. -> 즉, 구현체가 필요하다. ex) Hibernate, Eclipse Link 등

Spring에서는 JPA를 사용할 때, 구현체를 직접 다루지 않고 구현체들을 좀 더 쉽게 다루고자 추상화시킨 Spring Data JPA 모듈을 사용한다.

 

 

Spring Data JPA

  • 구현체들을 좀 더 쉽게 다루고자 추상화시킨 모듈
  • 사용시 JPA <- Hibernate <- Spring Data JPA 순으로 접근
  • 구현체 교체의 용이성(=Hibernate 외에 다른 구현체로 쉽게 교체 가능)  
    • Spring Data JPA 내부에서 구현체 매핑을 지원해주기 때문에 구현체 교체가 용이하다. 그래서  Hibernate와 큰 차이가 없음에도 Spring 팀에서 사용을 권장하고 있다.
  • 저장소 교체의 용이성 (=관계형 데이터베이스 외에 다른 저장소로 쉽게 교체 가능)
    • Spring Data의 하위 프로젝트들은 기본적인 CRUD 인터페이스가 같아  의존성만 교체하면 된다.

 

* 단점: 높은 러닝 커브 (JPA를 잘 쓰기 위해 객체지향 프로그래밍과 관계형 데이터베이스를 모두 이해해야 하기 때문) 

 

 

 

Spring Data JPA 적용 및 테스트

build.gradle에 아래 의존성을 등록한다.

//스프링 부트용 Spring Data Jpa 추상화 라이브러리, 스프링 부트 버전에 맞춰 자동으로 JPA 관련 라이브러리들의 버전 관리
implementation('org.springframework.boot:spring-boot-starter-data-jpa');
//인메모리 관계형 데이터베이스, 별도 설치 필요x, 메모리에서 실행 -> 애플리케이션 재시작할 때마다 초기화
implementation('com.h2database:h2');

 

Entity 생성 Posts.java (com.project.webservice.domain.posts)

위 코드에 작성된 롬북의 어노테이션 3가지

- @NoArgsConstructor: 기본 생성자 자동 추가

- @Entity: 테이블과 링크될 클래스라는 의미 ex) Posts.java -> posts table로 DB에 생성

- @Builder: 클래스의 빌더 패턴 클래스 생성, 생성자를 상단에 선언하면 생성자에 포함된 필드만 빌더에 포함한다.

 

Entity 클래스는 실제 DB의 테이블과 매칭될 클래스로 JPA를 사용하게 되면 DB 데이터에 작업시 실제 쿼리를 날리기보다는 Entity 클래스의 수정을 통해 작업한다.

Entity 클래스는 setter 메소드를 생성하지 않는다.  해당 클래스의 인스턴스 값의 변화가 어디서 이루어지는지 명확하게 구분할 수 없어 기능 변경이 복잡해지는 일을 방지하기 위해서이다.

->setter 메소드를 대신해서 목적과 의도를 나타낼 수 있는 메소드를 추가해서 사용한다.

또한 위 코드에서는 생성자 대신 빌더를 사용해서 어느 필드에 어떤 값을 넣어야 하는지 명확하게 인지할 수 있다.

 

DB Layer에 접근할 Repository 생성  PostsRepository.java (com.project.webservice.domain.posts)

인터페이스를 생성 후  JPARepository<Entity 클래스, PK 타입>를 상속하면 기본적인 CRUD 메서드가 자동으로 생성된다.

* Enitiy 클래스와 기본 Entity Repository는 같은 위치에 함께 있어야 한다.

 

Spring Data JPA 테스트 - PostsRepositoryTest.java (com.project.webservice.domain.posts) test 패키지에 생성

- @SpringBootTest: 별다른 설정없이 실행하면 자동으로 H2 데이터베이스를 실행한다.

- @AfterEach: Junit에서 단위테스트가 끝날때마다 수행되는 메소드라는 의미로 test가 종료될 때마다 저장된 데이터를 비우는 메소드를 지정하고 있다. 테스트가 순서 상관없이 실행되기 때문에 데이터간의 의존 관계가 없어야 한다.

책에서는 @After이라고 되어있는데, Junit5가 되면서 변경되었다.

 

테스트 결과 성공

 

실제로 실행된 쿼리를 보려면  src/main/resources/application.properties 파일을 생성해서 아래 코드를 입력한다.

이때  디버깅을 위해 출력되는 쿼리 로그를 MySQL 버전으로 변경한다.

spring.jpa.show_sql=true

# Change query log to MySql version
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:testdb;MODE=MYSQL
spring.datasource.hikari.username=sa

이 부분도 책과 다른데, Spring Boot 2.1.10 부터 MySQL 문법 지정 방식이 변화했기 때문이다. 책 버전은 2.1.7이라 2.1.10이상 버전을 사용하고 있다면 위와 같이 작성해주어야 한다.

다시 테스트 실행시 쿼리 확인 가능

 

 

 

 

등록/수정/조회 API 만들기

API 생성을 위한 클래스

  • Dto: Request 데이터를 받는다.
  • Controller: API 요청을 받는다.
  • Service: 트랜잭션, 도메인 기능 간의 순서를 보장한다.

* Web(controller),Service, Repository, Dto, Domain 이 5가지의 레이어에서 비지니스 로직을 처리하는 곳은 Domain이다.

 

PostsApiController 생성 (com.project.webservice.web)

 

PostsService 생성 (com.project.webservice.service)

- @RequiredArgsConstructor: final이 선언된 모든 필드를 인자값으로 하는 생성자를 생성해준다. 

@Autowired가 아닌 생성자를 통해 Bean을 주입받는다.

 

PostsSaveRequestDto 생성 (com.project.webservice.web.dto)

Entity 클래스와 거의 유사한 형태이지만 따로 생성한 이유는 Entity 클래스를 Request/Response 클래스로 사용해서는 안되기 때문이다. -> 데이터베이스와 맞닿아 있어 변경시 여러 클래스에 영향을 끼칠 수 있다.

 

PostsApiControllerTest 생성 ((com.project.webservice.web) test 패키지에 생성

이미지로 한번에 첨부하면 글씨가 너무 작아 소스코드 첨부로 변경

@ExtendWith(SpringExtension.class)
//HelloControllerTest와 달리 @WebMvcTest 사용x -> @WebMvcTest는 JPA 기능은 작동하지 않기 때문
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class PostsApiControllerTest {

    @LocalServerPort
    private int port;

    @Autowired
    private TestRestTemplate restTemplate;
    @Autowired
    private PostsRepository postsRepository;

    public void tearDown() throws Exception {
        postsRepository.deleteAll();
    }

    @Test
    public void Posts_등록된다() throws Exception {
        //given
        String title = "title";
        String content = "content";
        PostsSaveRequestDto requestDto = PostsSaveRequestDto.builder()
                .title(title)
                .content(content)
                .author("author")
                .build();

        String url = "http://localhost:" + port + "/api/v1/posts";

        //when
        ResponseEntity<Long> responseEntity = restTemplate.postForEntity(url, requestDto, Long.class);

        //then
        assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(responseEntity.getBody()).isGreaterThan(0L);

        List<Posts> all = postsRepository.findAll();
        assertThat(all.get(0).getTitle()).isEqualTo(title);
        assertThat(all.get(0).getContent()).isEqualTo(content);

    }
}

- @StringBootTest: JPA 기능까지 한 번에 테스트할 경우 @SpringBootTest와 TestRestTemplate를 사용한다.

@WebMvcTest은 JPA 기능이 작동하지 않고, Controller 등 외부 연동과 관련된 부분만 활성화되서 여기서는 사용하지 않는다.

테스트 결과 성공

 

 

수정, 삭제 api 및 테스트 작성

PostsApiController에 게시글 수정, 삭제 api 추가로 작성

 

ResponseDto 생성

Entity를 직접 처리하는 방법은 좋지 않고 Dto는 Entity 필드의 일부만 사용하므로 굳이  모든 필드를 가진 생성자가 필요하지 않다. -> Dto는 Entity를 받아서 처리

 

PostsUpdateRequestDto 생성 및 수정하는 메소드 update() Posts.java에 추가 작성

Posts.java update()

위에서 말했듯이 Entity 클래스는 setter 메소드를 생성하지 않는다.  해당 클래스의 인스턴스 값의 변화가 어디서 이루어지는지 명확하게 구분할 수 없어 기능 변경이 복잡해지는 일을 방지하기 위해서이다.

->setter 메소드를 대신해서 목적과 의도를 나타낼 수 있는 메소드 update()를 추가해서 사용한다.

수정, 조회 기능에 대한 코드는 일부 생략한다.

 

*JPA 데이터 update시 쿼리 날리는 부분 미존재

JPA 엔티티 매니저가 활성화된 상태로(Spring Data JPA를 쓰는 경우 기본옵션)
트랜잭션 안에서 데이터베이스에서 데이터를 가져오면 이 데이터는 영속성 컨텍스트가 유지된 상태이다.
이 상태에서 값을 변경하면 트랜잭션이 끝날 때, 테이블에 변경된 부분 반영
 -> Entity 객체의 값만 변경하면 데이터베이스에 쿼리를 날릴 필요가 없다. (=dirty checking)
 -> update에서 데이터베이스에 쿼리를 날리는 부분이 존재하는 않는다.

 

 

 

 

h2 웹 콘솔 사용

조회기능을 실제로 톰캣을 실행해서 확인할 수도 있다. 여기서는 H2를 데이터베이스를 사용했기에 웹 콘솔로 접속해야 한다.

웹 콘솔 활성화를 위해 application.properties 코드를 추가한다.

# Web console option activation
spring.h2.console.enabled=true

 

 추가하고 Application 클래스의 main 메서드를 실행하고 웹 브라우저에서 http://localhost:8080/h2-console로 접속한다. JDBC URL에 jdbc:h2:mem:testdb를 입력 후 Connect 버튼을 눌러 관리 페이지로 이동한다.

H2를 관리할 수 있는 관리 페이지로 이동된다. 여기서 쿼리 입력후 run을 눌러서 실행할 수 있다.

 

 

댓글