(4) 리포지터리(Repository)

리포지터리로 질문 / 답변 데이터 관리하기


시작하기 전에

개요 : 질문과 답변을 할 수 있는 게시판 서비스를 스프링부트를 통해 만들어 본다.

학습사이트 : https://wikidocs.net/book/7601

예제 코드 : https://github.com/pahkey/sbb


리포지터리란

  • 엔티티에 의해 생성된 데이터베이스 테이블에 접근하는 메서드들(findAll, save 등)을 사용하기 위한 인터페이스
  • CRUD를 어떻게 처리할지 정의하는 계층

이전에 학습했던 엔티티는 DB의 골격을 만드는 것이기 때문에, 엔티티만으론 직접 DB에 데이터를 저장하거나 조회할 수 없다.

데이터 처리를 위해서는 실제 DB와 연동하는 JPA 리포지터리가 필요하다


JPA 리포지터리 세팅


QuestionRepository.java 인터페이스 생성

package com.mysite.sbb;
import org.springframework.data.jpa.repository.JpaRepository;

public interface QuestionRepository extends JpaRepository<Question, Integer>{

}
  • 리포지토리로 만들기 위해 JpaRepository 상속
  • 제네릭 타입 <Question, Integer>
    • 질문 엔티티에는 id(integer)가 pk이므로 지정해줘야 한다.


AnswerRepository.java 인터페이스 생성

package com.mysite.sbb;

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

public interface AnswerRepository extends JpaRepository<Answer, Integer> {

}
  • 리포지토리로 만들기 위해 JpaRepository 상속
  • 제네릭 타입 <Question, Integer>
    • 답변 엔티티에는 id(integer)가 pk이므로 지정해줘야 한다.

질문 데이터 관리하기


질문 데이터 확인하기 (C)

기본적인 리포지토리 세팅을 끝냈으니, 진짜 데이터가 테이블에 들어가는지 테스트해보자.

  • 데이터 저장
    • src/test/java/com/mysite/sbb/SbbApplicationTests.java 파일 수정
package com.mysite.sbb;

import java.time.LocalDateTime;

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

@SpringBootTest
class SbbApplicationTests {
	
	@Autowired
	private QuestionRepository questionRepository;

	@Test
	void testJpa() {
		Question q1 = new Question();
		q1.setSubject("sbb가 무엇인가요?");
		q1.setContent("sbb에 대해서 알고 싶습니다.");
		q1.setCreateDate(LocalDateTime.now());
		this.questionRepository.save(q1); // 첫번째 질문 저장
		
		Question q2 = new Question();
		q2.setSubject("스프링부트 모델 질문입니다.");
		q2.setContent("id는 자동으로 생성되나요?");
		q2.setCreateDate(LocalDateTime.now());
		this.questionRepository.save(q2); // 두번째 질문 저장
	}

}
  • @SpringBootTest
    • SbbApplicationTests클래스가 스프링부트 테스트 클래스임을 의미함
  • @Autowired
  • JUnit 테스트
    • JUnit이란
      • 테스트코드를 실행하기 위해 사용하는 자바의 테스트 프레임워크
      • 아래의 그림처럼 실행

        1

      • The file is locked: nio:/Users/pahkey/local.mv.db 오류 발생시 로컬서버 중지해야 테스트 가능
  • 저장된 데이터 확인
    • 서버 재시작 후 h2콘솔에서 데이터 확인
    • 연결 후 아래의 쿼리문 실행
select * from question

2


질문 데이터 조회하기 (R)

  • 데이터를 조회하는 여러 메서드를 알아보자

  • findAll

    • 테이블에 저장된 모든 데이터를 조회할때 사용하는 메서드
    • src/test/java/com/mysite/sbb/SbbApplicationTests.java 파일 수정 후 JUnit테스트

        	@Test
        	void testJpa() {
        		List<Question> all = this.questionRepository.findAll();
        		assertEquals(2, all.size());
              		
        		Question q = all.get(0);
        		assertEquals("sbb가 무엇인가요? ", q.getSubject());
        	}
        }
      
    • assertEquals(기대값, 실제값)
      • 기대값과 실제값이 동일하다면 테스트 성공
    • Question q = all.get(0);
      • all이라는 리스트의 인덱스 0번의 데이터를 가져온다


  • findByld
    • Question 엔티티의 Id값으로 데이터를 조회
    • src/test/java/com/mysite/sbb/SbbApplicationTests.java 파일 수정

        @Test
            void testJpa() {
                Optional<Question> oq = this.questionRepository.findById(1);
                if(oq.isPresent()) {
                	Question q = oq.get();
                	assertEquals("sbb가 무엇인가요?", q.getSubject());
                }
            }
      
    • Optional
      • null 처리를 유연하게 처리하기 위해 사용하는 클래스
      • isPresent()메서드로 Question엔티티의 id값이 1인 데이터가 null이 아닌지를 확인한 후에 실제 Question 객체 값을 get하여 assertEquals 메서드 실행


  • findBySubject
    • Question 엔티티의 내용(subject)값으로 데이터를 조회
    • findBySubject 메서드를 사용하려면 QuestionRepositoy인터페이스에 아래와 같은 메서드 선언 필요
      • Question findBySubject(String subject);
    • 제목으로 테이블 데이터 조회하기
    • src/test/java/com/mysite/sbb/SbbApplicationTests.java 파일 수정 후 JUnit테스트

        @Test
            void testJpa() {
        			// 내용이 "sbb가 무엇인가요?"인 질문 찾기
            	Question q = this.questionRepository.findBySubject("sbb가 무엇인가요?");
        			// 찾은 내용의 id가 1인지 비교하기
            	assertEquals(1, q.getId());
            }
      
    • 인터페이스에 findBySubject라는 메서드를 선언만 하고 구현은 하지 않았는데, 테스트가 통과되는 이유?


JPA가 쿼리를 만들고 실행하는 과정 살펴보기

  • 위의 findBySubject 메서드를 호출할 때 실제 어떤 쿼리가 실행되는지 살펴보자
  • 실행되는 쿼리를 확인하려면 application.properties파일에 아래의 코드 추가 필요
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.show_sql=true
  • 추가한 후 SbbApplicationTests파일을 JUnit 테스트
  • 콘솔창에 아래와 같은 결과 확인 가능

3

  • 실행된 쿼리의 where 조건에 subject가 포함된 것을 확인할 수 있다


질문 데이터 수정하기 (U)

  • 데이터를 수정하는 테스트를 진행해보자
  • src/test/java/com/mysite/sbb/SbbApplicationTests.java 파일 수정
@Test
    void testJpa() {
    	Optional<Question> oq = this.questionRepository.findById(1);
    	assertTrue(oq.isPresent());
    	Question q = oq.get();
    	q.setSubject("수정된 제목");
    	this.questionRepository.save(q);
    }
  • 수정된 데이터 확인하기

4


질문 데이터 삭제하기(D)

  • 첫 번째 질문을 삭제하는 테스트
  • src/test/java/com/mysite/sbb/SbbApplicationTests.java 파일 수정
@Test
    void testJpa() {
			// 데이터가 2개인지 확인
    	assertEquals(2, this.questionRepository.count());

			// questionRepository에서 id가 1인것을 찾음
    	Optional<Question> oq = this.questionRepository.findById(1);
    	assertTrue(oq.isPresent());

			// id가 1인 데이터 삭제
    	Question q = oq.get();
    	this.questionRepository.delete(q);

			// 삭제가 되어 데이터가 1개인지 확인
    	assertEquals(1, this.questionRepository.count());
    }
  • 삭제된 데이터 확인

2


답변 데이터 관리하기


답변 데이터 생성후 저장하기 (C)

  • src/test/java/com/mysite/sbb/SbbApplicationTests.java 파일 수정
package com.mysite.sbb;

import static org.junit.jupiter.api.Assertions.assertTrue;

import java.time.LocalDateTime;
import java.util.Optional;

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

@SpringBootTest
class SbbApplicationTests {

		// 객체 DI주입
    @Autowired
    private AnswerRepository answerRepository;
    
    @Autowired
    private QuestionRepository questionRepository;

    @Test
    void testJpa() {
			// 어떤 질문에 대한 대답인가? : ID가 2번인 질문
    	Optional<Question> oq = this.questionRepository.findById(2);
    	assertTrue(oq.isPresent());
    	Question q = oq.get();
    	
			// Answer 객체 생성
    	Answer a = new Answer();

			// Answer 객체 작성 후 저장
    	a.setContent("네 자동으로 생성됩니다.");
    	a.setQuestion(q);
    	a.setCreateDate(LocalDateTime.now());
    	this.answerRepository.save(a);
    }
}
  • 답변 데이터를 생성하려면 무슨 질문에 대한 답변인지를 알아야 한다
    • findById() 메서드로 질문 데이터 구함
  • 생성된 답변 데이터 확인

    5


답변 조회하기 (R)

@Test
    void testJpa() {
			// 답변테이블 id가 1인 데이터 가져오기
    	Optional<Answer> oa = this.answerRepository.findById(1);
    	assertTrue(oa.isPresent());
    	Answer a = oa.get();

			// 가져온 데이터의 질문테이블 id가 2인지 확인하기
    	assertEquals(2, a.getQuestion().getId());
    }
  • Answer 엔티티의 question속성 (getQuestion)을 이용하면 답변에 연결된 질문을 조회할 수 있다.
  • 반대로 질문에 달린 답변도 찾기가 가능하다


질문에 달린 답변 찾기

@Test
    void testJpa() {
    	// 질문id가 2인 데이터 조회
    	Optional<Question> oq = this.questionRepository.findById(2);
    	assertTrue(oq.isPresent());
    	Question q = oq.get();
    	
    	// 질문에 달린 답변들 리스트
    	List<Answer> answerList = q.getAnswerList();
    	
    	// 리스트 확인
    	assertEquals(1, answerList.size());
    	assertEquals("네 자동으로 생성됩니다.", answerList.get(0).getContent());
    }
  • 테스트 실패
    • 이유 : 실제 서버에서는 JPA프로그램들을 실행할 때는 DB세션이 끊어지지 않지만, 테스트 코드에서는 Question 리포지터리가 findById를 호출하여 데이터를 조회하고 나면 DB세션이 끊어지기 때문
    • 즉, 다음 메서드인 getAnswerList에서 오류가 난다


  • 테스트 코드에서 오류 방지하는법
    • @Transactional 에너테이션 사용 : DB 세션이 유지된다
              // Transactional 에너테이션 추가
              @Transactional
          @Test
          void testJpa() {
              // 질문id가 2인 데이터 찾기
              Optional<Question> oq = this.questionRepository.findById(2);
              assertTrue(oq.isPresent());
              Question q = oq.get();
                
              // 질문에 달린 답변들 리스트
              List<Answer> answerList = q.getAnswerList();
                
              // 리스트 확인
              assertEquals(1, answerList.size());
              assertEquals("네 자동으로 생성됩니다.", answerList.get(0).getContent());
                
          }
      }
    

Leave a comment