점프 투 스프링부트 | 2장 Repository

5w31892p·2022년 12월 29일
0

Spring

목록 보기
18/30

:: Repository

  • entity에 의해 생성된 db Table에 접근하는 메서드들을 사용하기 위한 인터페이스
    • 메서드 (findAll, save...)
  • entity만으로는 DB에 데이터 저장하거나 조회가 불가능하다.
  • 데이터 처리를 위해 실제 데이터 베이스와 연동하는 JPA Repository 필요

JPARepository 상속 할 때는 제네릭스 타입으로 <EntityName, PKType>

  • PK = id
  • id type = Long

:: Test - JUnit

  • 작성한 Repository 테스트
  • 서버가 이미 구동 중이라면 중지 후 테스트 진행해야 한다.
  • 테스트 실행 결과 모두 성공이라면 H2 접속해서 확인하기

:: 데이터 저장하기

...
@Autowired
private QuestionRepository questionRepository;

@Test
void testJpa() {        
	Question question = new Question();
	question.setSubject("sbb가 무엇인가요?"); // 제목
	question.setContent("sbb에 대해서 알고 싶습니다."); // 내용
	question.setCreateDate(LocalDateTime.now()); // 작성 시간
	this.questionRepository.save(question);  // 저장
    
    Question question2 = new Question();
    question2.setSubject("스프링부트 모델 질문입니다.");
    ...
}

:: 데이터 조회하기

findAll() : 모든 데이터 조회

  • assertEquals(기대값, 실제값) : 기대값과, 실제값이 동일한지 조사
    • 동일하지 않다면 테스트 실패
    • JUnit의 메서드이다.
...
@Test
void testJpa() {
	List<Question> all = this.questionRepository.findAll(); // 저장된거 다 찾아와
    assertEquals(2, all.size()); // 전체가 2일텐데 실제로도 맞나?
    
    Question q = all.get(0); // 젤 처음꺼 가져와바
    assertEquals("sbb가 무엇인가요?", q.getSubject()); // 젤 처음꺼 제목 저거일텐데 맞지?
}

findById() : entity의 id값으로 데이터 조회

  • isPresent() : null 체크 (Optional과 함께 쓰인다.)
    • 값 존재하면 true
    • 값 없으면 flase
  • Optional : NullPointerException 방지
    • null이 올 수 있는 값을 감싸는 Wrapper 클래스로 NPE 발생하지 않도록 도와준다.
    • isPresent()메소드로 null 인지 아닌지 확인 후 get으로 실제 Entity 객체 값을 얻어야 한다.
@Test
void testJpa() {
	Optional<Question> oq = this.questionRepository.findById(1); // id값 1번인거 가져와봐
    if(oq.isPresent()) { // null 아니면
    	Question q = oq.get(); // 아까 찾은 1번 줘
        assertEquals("sbb가 무엇인가요?", q.getSubject()); // 1번 제목 저거 맞지?
    }
}

findBy + Entity속성이름 : 해당 속성 값으로 데이터 조회

  • Repository에 명시해야한다.
  • 명시해주면 해당 메서드 실행될 때 JPA가 분석하여 쿼리 만들고 실행한다.
    • where 조건에 해당 속성이 포함된 쿼리 생성
@Test
void testJpa() {
	Question q = this.questionRepository.findBySubject("sbb가 무엇인가요?"); // 제목이거인거 찾아줘
    assertEquals(1, q.getId()); // 이거 1번 맞지?
}

findBy + Entity속성이름1 + And + Entity속성이름2

  • 두개의 속성을 And 조건으로 조회
  • Repository에 추가
  • 쿼리 또한 and 조건으로 where문에 사용
@Test
void testJpa() {
	Question q = this.questionRepository.findBySubjectAndContent("sbb가 무엇인가요?", , "sbb에 대해서 알고 싶습니다."); // 제목이랑 내용 저거인거 찾아줘
    assertEquals(1, q.getId()); // 이거 1번 맞지?
}

find + Entity속성이름1 + Like

  • Like 검색을 위해서는 % 표기해줘야 함
    • sbb% : "sbb"로 시작하는 문자열
    • %sbb : "sbb"로 끝나는 문자열
    • %sbb% : "sbb"를 포함하는 문자열
@Test
void testJpa() {
	List<Question> qList = this.questionRepository.findBySubjectLike("sbb%"); // sbb로 시작하는 제목 찾아줘
    Question q = qList.get(0);  // 그 중에 젤 첫번째꺼 줘~
    assertEquals("sbb가 무엇인가요?", q.getSubject()); // 그거 제목 이거 맞지?
}

:: 데이터 수정하기

  • assertTrue(값) : 값이 true인지 테스트
  • update 쿼리 실행됨
@Test
void testJpa() {
	Optional<Question> oq = this.questionRepository.findById(1); // id 1번 찾기
    assertTrue(oq.isPresent()); // 찾은게 null인지 아닌지 테스트
    Question q = oq.get(); // 수정할 객체에 가져온 거 넣기
    q.setSubject("수정된 제목"); // 수정할 내용 넣기
    this.questionRepository.save(q); // 저장!
}

:: 데이터 삭제하기

  • count() : Repository의 총 데이터 건수 리턴하는 메서드
@Test
void testJpa() {
    assertEquals(2, this.questionRepository.count()); // 총 두개인지 확인
    Optional<Question> oq = this.questionRepository.findById(1); // id 1번 가져오기
    assertTrue(oq.isPresent()); // null인지 아닌지 확인
    Question q = oq.get(); // 삭제할 객체에 가져온 거 넣기
    this.questionRepository.delete(q); // 삭제
    assertEquals(1, this.questionRepository.count()); // 남은거 1개인지 확인
}

:: 댓글 생성하기

  • 생성 삭제 조회 수정 등 다 위와 똑같지만, 어떤 게시글에 댓글 남길 것인지 알아야 한다.
  • 그래서 questionRepository에서 findById로 해당 id 가져온 와야 한다.
  • 그리고 저장 할 때에도 어떤 질문의 답변인지 확실히 하기 위해 Qustion 객체가 필요하다.
@Test
void testJpa() {
    Optional<Question> oq = this.questionRepository.findById(2); // 2번 질문 가져오기
    assertTrue(oq.isPresent()); // null 확인
    Question q = oq.get(); // 가져온 것 객체에 담기

    Answer a = new Answer();
    a.setContent("네 자동으로 생성됩니다."); // 댓글 생성
    a.setQuestion(q);  // 어떤 질문의 답변인지 알기위해서 Question 객체가 필요
    a.setCreateDate(LocalDateTime.now()); // 작성일시
    this.answerRepository.save(a); // 저장
}

:: 답변에 연결된 질문 찾기 VS 질문에 달린 답변 찾기

답변에 연결된 질문 찾기

  • Answer에 question 속성 정의되어 있어 찾기 쉬움

질문에 달린 답변 찾기

  • Question에 정의한 answerList 사용하면 찾기 쉬움 (@OneToMany로 연결)
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());

이렇게 하면 테스트 실패 뜬다.
Question 조회 후 db세션 끝난다.
답변리스트가 q객체 조회할 때 가져와야 하는데, q.getAnswerList() 메서드 호출할 때 가져오기 때문에 실패가 뜬다.

이럴 때 데이터 가져오는 방식이 있는데, fetch=FetchType.EAGER 를 사용하면 된다.

@OneToMany@ManyToOne 의 옵션

  • fetch=FetchType.LAZY : 필요한 시점에 데이터 가져오는 방식
  • fetch=FetchType.EAGER : q 객체 조회할 때 답변 리스트 모두 가져오는 방식

또 다른 방식으론 @Transactional 사용하는 것이다.
@Transactional 사용하면 메서드가 종료될 때까지 DB 세션이 유지되기 되기 때문에 해결된다.

@Transactional
@Test
void testJpa() {
...

:: 데이터 조회 메서드 정리

  • findAll() : 저장된 모든 데이터 조회
  • findById() : entity의 id값으로 데이터 조회
  • findBy + Entity속성이름 : 메서드 제공하지 않기 때문에 Repository에 넣어야 한다.
// ex. findBySubject
	Question findBySubject(String subject);
  • findBy + Entity속성이름1 + And + Entity속성이름2 : Repository에 추가
// ex. findBySubjectAndContent
	Question findBySubjectAndContent(String subject, String content);
  • find + Entity속성이름1 + Like : Repository에 추가
// ex. findBySubjectLike
	List<Question> findBySubjectLike(String subject);

항목설명예제
And여러 컬럼을 and 로 검색findBySubjectAndContent(String subject, String content)
Or여러 컬럼을 or 로 검색findBySubjectOrContent(String subject, String content)
Between컬럼을 between으로 검색 findByCreateDateBetween(LocalDateTime fromDate, LocalDateTime toDate)
LessThan작은 항목 검색findByIdLessThan(Integer id)
GreaterThanEqual크거나 같은 항목 검색findByIdGraterThanEqual(Integer id)
Likelike 검색findBySubjectLike(String subject)
In여러 값중에 하나인 항목 검색findBySubjectIn(String[] subjects)
OrderBy검색 결과를 정렬하여 전달findBySubjectOrderByCreateDateAsc(String subject)

응답 결과가 여러건인 경우에는 returnType을 Entity가 아닌 List<Entity>로 해야 한다.


:: @SpringBootTest

  • 현재 클래스가 스프링부트 테스트 클래스임을 알려준다.

:: @Autowired

  • 객체 주입
  • 스프링의 DI 기능 - 객체 자동 생성
  • 순환참조 문제와 같은 이유로 생성자를 통한 객체 주입방식이 권장됨
  • 테스트에서는 생성자로 객체 주입 불가능 하므로 테스트 코드 작성시에만 사용

:: @Test

  • 해당 메서드가 테스트 메서드임을 나타낸다.
  • JUnit 실행하면 @Test 붙은 메서드가 실행된다.

:: application.properties

#JPA
# 메시지 호출 시 실행되는 쿼리 로그 보는 것
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.show_sql=true

0개의 댓글