(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
- 스프링의 DI(Dependency Injection, 의존주입)기능
- 의존주입 : 스프링이 객체를 대신 생성하여 주입한다.
- 개념 설명 참조 : https://tecoble.techcourse.co.kr/post/2021-04-27-dependency-injection/
- questionRepository 객체를 스프링이 자동으로 생성해 줌
- 스프링의 DI(Dependency Injection, 의존주입)기능
- JUnit 테스트
- JUnit이란
- 테스트코드를 실행하기 위해 사용하는 자바의 테스트 프레임워크
-
아래의 그림처럼 실행
- The file is locked: nio:/Users/pahkey/local.mv.db 오류 발생시 로컬서버 중지해야 테스트 가능
- JUnit이란
- 저장된 데이터 확인
- 서버 재시작 후 h2콘솔에서 데이터 확인
- 연결 후 아래의 쿼리문 실행
select * from question
질문 데이터 조회하기 (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라는 메서드를 선언만 하고 구현은 하지 않았는데, 테스트가 통과되는 이유?
- DI에 의해 스프링이 자동으로 QuestionRepository 객체를 생성하고 메서드를 실행시킨다
- 이때 JPA가 해당 메서드명(findby + (엔티티의 속성명) )을 분석하여 쿼리를 만들고 실행한다
- 엔티티의 속성명에 대한 공식문서
JPA가 쿼리를 만들고 실행하는 과정 살펴보기
- 위의 findBySubject 메서드를 호출할 때 실제 어떤 쿼리가 실행되는지 살펴보자
- 실행되는 쿼리를 확인하려면 application.properties파일에 아래의 코드 추가 필요
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.show_sql=true
- 추가한 후 SbbApplicationTests파일을 JUnit 테스트
- 콘솔창에 아래와 같은 결과 확인 가능
- 실행된 쿼리의 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);
}
- 수정된 데이터 확인하기
질문 데이터 삭제하기(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());
}
- 삭제된 데이터 확인
답변 데이터 관리하기
답변 데이터 생성후 저장하기 (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() 메서드로 질문 데이터 구함
-
생성된 답변 데이터 확인
답변 조회하기 (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