관리 메뉴

거니의 velog

(7) 스프링 부트와 API 서버 2 본문

SpringBoot_React 풀스택 프로젝트

(7) 스프링 부트와 API 서버 2

Unlimited00 2024. 2. 28. 11:24

2. TodoRepository 테스트

* 스프링 부트는 기본적으로 테스트 환경을 지원하기 때문에 이미 생성되어 있는 test 폴더를 이용해서 테스트 코드를 작성하고 실행할 수 있다. 프로젝트에서는 Lombok의 로그를 좀 더 적극적으로 활용하기 위해서 테스트 환경에서도 Lombok을 이용할 수 있는 설정으로 변경한다.

dependencies {
    (...)
    
    testCompileOnly 'org.projectlombok:lombok'
    testAnnotationProcessor 'org.projectlombok:lombok'
}

* build.gradle의 설정은 프로젝트 전체에 영향을 주기 때문에 주의해야 한다. build.gradle 설정을 변경한 후에는 새로운 설정이 반영될 수 있도록 작업 영역을 초기화해야 한다.

* 'View -> Command Palette' 메뉴에서 'clean' 키워드로 검색해서 'Server Workspace' 항목을 선택하고 VSCode를 다시 시작한다.

* TDD( test-driven development , 테스트 주도 개발)에 대한 개념은 하단의 포스트를 참조한다.

https://www.incodom.kr/%ED%85%8C%EC%8A%A4%ED%8A%B8_%EC%A3%BC%EB%8F%84_%EA%B0%9C%EB%B0%9C

 

생물정보 전문위키, 인코덤

Wikipedia for Bioinformatics

www.incodom.kr

* Test 폴더에는 repository 패키지를 생성하고 TodoRepositoryTests.java 파일을 추가한다.

package com.unlimited.mallapi.repository;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import lombok.extern.log4j.Log4j2;

@SpringBootTest
@Log4j2
public class TodoRepositoryTests {

    // TodoRepository 주입
    @Autowired
    private TodoRepository todoRepository;

    // TodoRepository가 정상적으로 주입되었는지 확인하는 테스트
    @Test
    public void test1() {
        log.info("---------------------------");
        log.info(todoRepository);
    }

}

* 테스트 메서드는 메서드 오른쪽의 표시를 이용해서 실행하거나 VSCode 오른쪽에 있는 메뉴 중에 테스트 메뉴에서 test1()을 확인하고 실행할 수 있다.

* 실행 결과로 TodoRepository 타입의 객체가 생성된 것을 확인한다(원래의 테스트 주도 개발 방식에서는 Assertions의 API를 활용하는 것이 좋지만, 예제에서는 눈으로 확인할 수 있도록 출력하는 방식을 이용한다).

* 디버그 콘솔에서 출력되니 주의하자!


(1) 데이터 추가

* JPA는 엔티티 클래스에서 만들어진 엔티티 객체를 저장하는 것으로 데이터베이스의 insert나 update가 실행될 수 있다. 새로운 데이터를 추가하는 것은 Todo 엔티티 객체를 생성한 후에 TodoReposiroty의 save() 메서드를 이용한다.

* TodoRepositoryTests 클래스에 데이터를 추가하는 테스트 코드를 추가한다.

package com.unlimited.mallapi.repository;

import java.time.LocalDate;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import com.unlimited.mallapi.domain.Todo;

import lombok.extern.log4j.Log4j2;

@SpringBootTest
@Log4j2
public class TodoRepositoryTests {

    // TodoRepository 주입
    @Autowired
    private TodoRepository todoRepository;

    // TodoRepository가 정상적으로 주입되었는지 확인하는 테스트
    @Test
    public void test1() {
        log.info("---------------------------");
        log.info(todoRepository);
    }

    // 할일 데이터를 100개 생성하여 저장하는 테스트
    @Test
    public void testInsert() {
        for (int i = 1; i <= 100; i++) {
            // 할일 객체 생성
            Todo todo = Todo.builder()
                    .title("title..." + i)           // 할일 제목 설정
                    .dueDate(LocalDate.of(2024, 3, 1)) // 마감일 설정
                    .writer("user00")                  // 작성자 설정
                    .build();

            // 할일 저장
            todoRepository.save(todo);
        }
    }

}

* testInsert()가 실행되면 100개의 insert 문이 실행되는 것을 확인할 수 있다.

* 데이터베이스를 조회할 수 있는 툴을 이용해서 tbl_todo 테이블에 추가된 데이터를 확인할 수 있다(데이터베이스 툴에 따라서 boolean 타입은 다르게 보일 수 있다. 예를 들어 DataGrip 제품의 경우에는 true/false/로 표시된다).

* 간혹 잘못된 코드를 실행해서 다시 테이블을 만들어야 한다면 데이터베이스 내에서 테이블을 삭제하고
  테스트 코드를 실행하면 테이블 생성과 데이터 추가는 함께 처리된다.

(2) 데이터 조회

* 특정한 번호와 데이터를 알아내기 위해서는 findById() 메서드 기능을 이용한다. TodoRepositoryTests 클래스에 테스트 코드를 추가한다. findById() 기능은 리턴 타입이 java.util.Optional 이다.

package com.unlimited.mallapi.repository;

import java.time.LocalDate;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import com.unlimited.mallapi.domain.Todo;

import lombok.extern.log4j.Log4j2;

@SpringBootTest
@Log4j2
public class TodoRepositoryTests {

    @Autowired
    private TodoRepository todoRepository;

    @Test
    public void test1() {
        log.info("---------------------------");
        log.info(todoRepository);
    }

    @Test
    public void testInsert() {
        for (int i = 1; i <= 100; i++) {
            Todo todo = Todo.builder()
                    .title("title..." + i)
                    .dueDate(LocalDate.of(2024, 3, 1))
                    .writer("user00")
                    .build();

            todoRepository.save(todo);
        }
    }

    // 특정 번호의 할일을 조회하는 테스트
    @Test
    public void testRead() {
        // 존재하는 번호로 확인
        Long tno = 33L; // 1~100 사이의 번호 중 33번 조회
        java.util.Optional<Todo> result = todoRepository.findById(tno);
        Todo todo = result.orElseThrow();
        log.info(todo);
    }

}

* 테스트에서 사용하는 번호는 데이터베이스에서 존재하는 번호로 확인한다. 테스트 중에 'select...' 문이 실행되는 것을 확인할 수 있다.


(3) 데이터 수정

* 데이터 수정은 등록과 동일하게 save() 메서드로 수정한다. 일반적으로 엔티티 객체는 가능하면 불변(immutable)하게 만들어지는 것이 좋지만 상황에 따라서 수정이 가능한 객체를 만들기도 한다.

* Todo 엔티티 클래스에서는 수정이 가능한 부분에 대해서 변경 가능하게 수정한다. Todo의 제목(title)이나 완료 여부(complete), 만료일(dueDate) 등은 수정할 수 있다.

package com.unlimited.mallapi.domain;

import java.time.LocalDate;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Entity
@Table(name = "tbl_todo")
@Getter
@ToString
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Todo {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long tno;
    private String title;
    private String writer;
    private boolean complete;
    private LocalDate dueDate;

    // 제목 변경 메서드
    // setter의 재정의(override)를 피하기 위해 setTitle로 명명하지 않고 changeTitle로 명명
    public void changeTitle(String title) {
        this.title = title;
    }

    // 완료 여부 변경 메서드
    public void changeComplete(boolean complete) {
        this.complete = complete;
    }

    // 마감일 변경 메서드
    public void changeDueDate(LocalDate dueDate) {
        this.dueDate = dueDate;
    }

}

* 테스트 코드에서는 
  1) 우선 해당 번호의 데이터를 조회한 후에
  2) 필요한 내용을 변경하고
  3) save()를 실행한다.

package com.unlimited.mallapi.repository;

import java.time.LocalDate;
import java.util.Optional;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import com.unlimited.mallapi.domain.Todo;

import lombok.extern.log4j.Log4j2;

@SpringBootTest
@Log4j2
public class TodoRepositoryTests {

    // TodoRepository 주입
    @Autowired
    private TodoRepository todoRepository;

    // TodoRepository가 정상적으로 주입되었는지 확인하는 테스트
    @Test
    public void test1() {
        log.info("---------------------------");
        log.info(todoRepository);
    }

    // 할일 데이터를 100개 생성하여 저장하는 테스트
    @Test
    public void testInsert() {
        for (int i = 1; i <= 100; i++) {
            // 할일 객체 생성
            Todo todo = Todo.builder()
                    .title("title..." + i)           // 할일 제목 설정
                    .dueDate(LocalDate.of(2024, 3, 1)) // 마감일 설정
                    .writer("user00")                  // 작성자 설정
                    .build();

            // 할일 저장
            todoRepository.save(todo);
        }
    }

    // 특정 번호의 할일을 조회하는 테스트
    @Test
    public void testRead() {
        // 존재하는 번호로 확인
        Long tno = 33L; // 1~100 사이의 번호 중 33번 조회
        Optional<Todo> result = todoRepository.findById(tno);
        Todo todo = result.orElseThrow();
        log.info(todo);
    }

    // 특정 번호의 할일을 수정하는 테스트
    @Test
    public void testModify() {
        Long tno = 33L;
        Optional<Todo> result = todoRepository.findById(tno); // import java.util.Optional; 로 추가

        Todo todo = result.orElseThrow();
        todo.changeTitle("Modified 33...");                     // 제목 변경
        todo.changeComplete(true);                               // 완료 여부 변경
        todo.changeDueDate(LocalDate.of(2023, 10, 10));          // 마감일 변경

        todoRepository.save(todo);
    }

}

* 위의 테스트 코드에서는 findById() 에서 한 번의 select가 이루어지고 save() 하기 위해서 다시 select와 update가 이루어진다.

findById() 에서 한 번의 select
save() 하기 위해서 다시 select
save() 하기 위해서 update

* 테스트 실행 후에는 데이터베이스에서 업데이트 결과를 확인해 보자.


(4) 데이터 삭제

* 데이터의 삭제는 deleteById()로 쉽게 처리할 수 있다. 삭제의 경우도 먼저 데이터베이스에서 select를 수행한 후에 delete가 실행된다.

package com.unlimited.mallapi.repository;

import java.time.LocalDate;
import java.util.Optional;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import com.unlimited.mallapi.domain.Todo;

import lombok.extern.log4j.Log4j2;

@SpringBootTest
@Log4j2
public class TodoRepositoryTests {

    // TodoRepository 주입
    @Autowired
    private TodoRepository todoRepository;

    // TodoRepository가 정상적으로 주입되었는지 확인하는 테스트
    @Test
    public void test1() {
        log.info("---------------------------");
        log.info(todoRepository);
    }

    // 할일 데이터를 100개 생성하여 저장하는 테스트
    @Test
    public void testInsert() {
        for (int i = 1; i <= 100; i++) {
            // 할일 객체 생성
            Todo todo = Todo.builder()
                    .title("title..." + i)           // 할일 제목 설정
                    .dueDate(LocalDate.of(2024, 3, 1)) // 마감일 설정
                    .writer("user00")                  // 작성자 설정
                    .build();

            // 할일 저장
            todoRepository.save(todo);
        }
    }

    // 특정 번호의 할일을 조회하는 테스트
    @Test
    public void testRead() {
        // 존재하는 번호로 확인
        Long tno = 33L; // 1~100 사이의 번호 중 33번 조회
        Optional<Todo> result = todoRepository.findById(tno);
        Todo todo = result.orElseThrow();
        log.info(todo);
    }

    // 특정 번호의 할일을 수정하는 테스트
    @Test
    public void testModify() {
        Long tno = 33L;
        Optional<Todo> result = todoRepository.findById(tno); // import java.util.Optional; 로 추가

        Todo todo = result.orElseThrow();
        todo.changeTitle("Modified 33...");                     // 제목 변경
        todo.changeComplete(true);                               // 완료 여부 변경
        todo.changeDueDate(LocalDate.of(2023, 10, 10));          // 마감일 변경

        todoRepository.save(todo);
    }

    // 특정 번호의 할일을 삭제하는 테스트
    @Test
    public void testDelete() {
        Long tno = 33L;
        todoRepository.deleteById(tno);
    }

}

33번 게시물이 잘 삭제되었다.

* JPA가 select를 먼저 실행한 후에 update/delete를 실행하는 이유는 JPA가 결과적으로 원하는 것이 애플리케이션의 데이터와 데이터베이스의 동기화이기 때문이다. JPA는 객체로 관리되는 상태를 데이터베이스에 자동으로 반영(commit)해 주는 데 이 때문에 데이터베이스만 접근하는 것이 아니라 데이터를 관리하는 존재(엔티티 매니저라고 한다)를 통해서 모든 작업이 이루어진다. 특정한 엔티티 객체를 변화시키기 위해서는 우선 엔티티 매니저의 관리하에 있어야만 하기 때문에 select 문을 통해서 메모리상으로 로딩하는 과정을 수행하게 된다.


(5) 페이징 처리

* 개발 단계에서 반드시 필요한 페이지 처리는 Spring Data JPA에서는 Pageable 타입을 사용해서 별도의 코드 작성 없이 페이징 처리를 할 수 있다. JpaRepository에는 findAll() 메서드를 통해서 한 번에 페이지에 대한 처리가 가능하다.

* findAll()의 파라미터 타입인 Pageable은 PageRequest.of(페이지 번호, 사이즈)의 형태로 생성하는데 주의할 점은 페이지 번호가 0부터 시작한다는 점을 주의해야 한다. findAll()의 결과는 Page<엔티티> 타입으로 생성되는데 데이터의 수가 충분하면 내부적으로 데이터베이스에 count 쿼리를 같이 실행한다.

* 테스트를 위해 testPaging() 메서드를 작성한다(import 주의).

package com.unlimited.mallapi.repository;

import java.time.LocalDate;
import java.util.Optional;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

import com.unlimited.mallapi.domain.Todo;

import lombok.extern.log4j.Log4j2;

@SpringBootTest
@Log4j2
public class TodoRepositoryTests {

    // TodoRepository 주입
    @Autowired
    private TodoRepository todoRepository;

    // TodoRepository가 정상적으로 주입되었는지 확인하는 테스트
    @Test
    public void test1() {
        log.info("---------------------------");
        log.info(todoRepository);
    }

    // 할일 데이터를 100개 생성하여 저장하는 테스트
    @Test
    public void testInsert() {
        for (int i = 1; i <= 100; i++) {
            // 할일 객체 생성
            Todo todo = Todo.builder()
                    .title("title..." + i)           // 할일 제목 설정
                    .dueDate(LocalDate.of(2024, 3, 1)) // 마감일 설정
                    .writer("user00")                  // 작성자 설정
                    .build();

            // 할일 저장
            todoRepository.save(todo);
        }
    }

    // 특정 번호의 할일을 조회하는 테스트
    @Test
    public void testRead() {
        // 존재하는 번호로 확인
        Long tno = 33L; // 1~100 사이의 번호 중 33번 조회
        Optional<Todo> result = todoRepository.findById(tno);
        Todo todo = result.orElseThrow();
        log.info(todo);
    }

    // 특정 번호의 할일을 수정하는 테스트
    @Test
    public void testModify() {
        Long tno = 33L;
        Optional<Todo> result = todoRepository.findById(tno); // import java.util.Optional; 로 추가

        Todo todo = result.orElseThrow();
        todo.changeTitle("Modified 33...");                     // 제목 변경
        todo.changeComplete(true);                               // 완료 여부 변경
        todo.changeDueDate(LocalDate.of(2023, 10, 10));          // 마감일 변경

        todoRepository.save(todo);
    }

    // 특정 번호의 할일을 삭제하는 테스트
    @Test
    public void testDelete() {
        Long tno = 33L;
        todoRepository.deleteById(tno);
    }

    // 페이징 처리를 테스트하는 메서드
    @Test
    public void testPaging() {
        // 페이징 처리를 위한 Pageable 설정
        // import org.springframework.data.domain.Pageable;
        Pageable pageable = PageRequest.of(0, 10, Sort.by("tno").descending());

        // 페이징 처리된 결과를 가져오기
        // import org.springframework.data.domain.Page;
        Page<Todo> result = todoRepository.findAll(pageable);

        log.info(result.getTotalElements());

        // 결과 목록 출력
        result.getContent().stream().forEach(todo -> log.info(todo));
    }

}

* 자바의 람다식(lambda)는 아래 포스트를 참고한다.

https://khj93.tistory.com/entry/JAVA-%EB%9E%8C%EB%8B%A4%EC%8B%9DRambda%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B4%EA%B3%A0-%EC%82%AC%EC%9A%A9%EB%B2%95

 

[JAVA] 람다식(Lambda)의 개념 및 사용법

람다함수란? 람다 함수는 프로그래밍 언어에서 사용되는 개념으로 익명 함수(Anonymous functions)를 지칭하는 용어입니다. 현재 사용되고 있는 람다의 근간은 수학과 기초 컴퓨터과학 분야에서의 람

khj93.tistory.com

* 위의 테스트 코드를 실행하면 데이터를 조회하는 쿼리에 limit이 적용되어 있고, 데이터의 개수를 가져오는 쿼리가 실행되는 것을 확인할 수 있다.

데이터를 조회하는 쿼리에 limit이 적용
데이터의 개수를 가져오는 쿼리

* 테스트 결과로 99개의 데이터가 존재(앞에서 1개 삭제함)하고 tno의 역순(내림차순 정렬, descending)으로 정렬된 것을 확인할 수 있다.


 

'SpringBoot_React 풀스택 프로젝트' 카테고리의 다른 글

(9) 스프링 부트와 API 서버 4  (0) 2024.02.28
(8) 스프링 부트와 API 서버 3  (0) 2024.02.28
(6) 스프링 부트와 API 서버 1  (0) 2024.02.26
(5) React-Router 3  (0) 2024.02.26
(4) React-Router 2  (0) 2024.02.26