Repository

suhan cho·2022년 6월 28일
0

Repository

  • 엔티티만으로는 데이터베이스에 데이터를 저장하거나 조회 할 수 없다.
  • 데이터 처리를 위해서는 실제 데이터베이스와 연동하는 JPA Repository가 필요하다
  • 엔티티에 의해 생성된 데이터베이스 테이블에 접근하는 메서드들(findAll, save등) 사용하기 위한 인터페이스이다.
  • CRUD를 어떻게 처리할지 정의하는 계층이 바로 리포지터리이다.
package com.mysite.sbb;

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

public interface QuestionRepository extends JpaRepository<Question, Integer> {
    
}
  • JpaRepository 인터페이스 상속 제네릭스 타입으로 <Question, Integer> 처럼 리포지터리의 대상이 되는 엔티티의 타입(Question)과 해당 엔티티의 PK의 속성 타입을 지정한다.
  • Question 엔티티의 PK(Primary Key)속성인 id의 타입은 Integer이다.

Test

package com.mysite.sbb;

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

import java.time.LocalDateTime;

@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);  // 두번째 질문 저장
	}

	void contextLoads() {
	}

}

@SpringBootTest

  • SbbApplicationTests 클래스가 스프링부트 테스트 클래스임을 의미

@Autowired

  • 스프링의 DI기능으로 questionRepository객체를 스프링이 자동으로 생성
  • 객체를 주입하기 위해 사용하는 스프링의 애너테이션
  • 객체 주입방식으로는 Setter과 생성자를 사용하는 방식이 있다.
  • 순환참조 문제와 같은 이유로 @Autowired보다는 생성자를 통한 객체 주입방식이 권장
  • 테스트코드의 경우에는 생성자를 통한 객체의 주입이 불가능하므로 테스트 코드 작성시에만 @Autowired사용 실제 코드는 생성자를 통한 객체 주입방식 사용

DI

  • 스프링이 객체를 대신 생성하여 주입한다.

데이터 조회하기

findAll(데이터 조회시 사용)

package com.mysite.sbb;

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

import java.time.LocalDateTime;
import java.util.List;

@SpringBootTest
class SbbApplicationTests {

	@Autowired
	private QuestionRepository questionRepository;

	@Test
	void testJpa() {
		List<Question> all = this.questionRepository.findAll();
		assertEquals(2, all.size());

		Question q = all.get(0);
		assertEquals("sbb가 무엇인가요?", q.getSubject());
	}
}
  • question테이블에 저장된 모든 데이터를 조회하기 위해서 리포지터리의 findAll메서드 사용
  • 총 2건을 저장했기에 사이즈가 2가 돼야한다.
  • 사이즈가 2인지 확인하기 위해 JUnit의 assertEquls(기댓값, 실제값) 사용

findById

  • Id값으로 데이터를 조회
package com.mysite.sbb;

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

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

@SpringBootTest
class SbbApplicationTests {

	@Autowired
	private QuestionRepository questionRepository;

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

findBySubject

package com.mysite.sbb;

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

public interface QuestionRepository extends JpaRepository<Question, Integer> {
    Question findBySubject(String subject);
}
  • Question 리포지터리는 findBySubject와 같은 메서드를 기본적으로 제공하지 않는다.
package com.mysite.sbb;

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

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

@SpringBootTest
class SbbApplicationTests {

	@Autowired
	private QuestionRepository questionRepository;

	@Test
	void testJpa() {
		Question q = this.questionRepository.findBySubject("ssb가 무엇인가요?");
		assertEquals(1,q.getId());
	}
}
  • 인터페이스에 findBySubject라는 메서드를 선언만 하고 구현하지 않았지만

  • JpaRepository 객체를 생성(DI에 의해 스프링이 자동으로 QuestionRepository객체 생성) 이를 프록시 패턴이라 한다.

  • 즉, findBy + 엔티티의 속성명과 같은 리포지터리 메서드를 작성하면 해당 속성의 값으로 데이터 조회 가능

데이터 수정하기

@SpringBootTest
class SbbApplicationTests {

	@Autowired
	private QuestionRepository questionRepository;

	@Test
	void testJpa() {
		Optional<Question> oq = this.questionRepository.findById(1);
		assertTrue(oq.isPresent());
		Question q = oq.get();
		q.setSubject("수정된 제목");
		this.questionRepository.save(q);
	}
}
  • 질문 데이터를 조회한 다음 subject를 수정된 제목이라는 값으로 수정

데이터 삭제하기

@SpringBootTest
class SbbApplicationTests {

	@Autowired
	private QuestionRepository questionRepository;

	@Test
	void testJpa() {
		assertEquals(2, this.questionRepository.count());
		Optional<Question> oq = this.questionRepository.findById(1);
		assertTrue(oq.isPresent());
		Question q = oq.get();
		this.questionRepository.delete(q);
		assertEquals(1, this.questionRepository.count());
	}
}

  • count() 해당 리포지터리의 총 데이터건수를 리턴한다.
  • assertEquals()로 삭제하기 전에 데이터 건수 2 후 1인지 테스트한 것

데이터 저장하기

@SpringBootTest
class SbbApplicationTests {

	@Autowired
	private AnswerRepository answerRepository;

	@Autowired
	private QuestionRepository questionRepository;

	@Test
	void testJpa() {
		Optional<Question> oq  = this.questionRepository.findById(2);
		assertTrue(oq.isPresent());
		Question q = oq.get();

		Answer a = new Answer();
		a.setContent("네 자동으로 생성됩니다");
		a.setQuestion(q); //어떤 질문의 답변인지 알기위해서 Question객체 필요
		a.setCreateDate(LocalDateTime.now());
		this.answerRepository.save(a);
	}
}
  • AnswerRepository객체를 @Autowired로 주입
  • 답변 데이터 생성하려면 질문 데이터가 필요하므로 id가 2인 데이터 가져오고 Answer 엔티티의 question속성에 방금 가져온 질문데이터 대입(a.setQuestion(q))

답변 조회하기

  • answer도 id 속성이 기본키이므로 값이 자동 생성 id값으로 데이터 조회
@SpringBootTest
class SbbApplicationTests {

	@Autowired
	private AnswerRepository answerRepository;

	@Autowired
	private QuestionRepository questionRepository;

	@Test
	void testJpa() {
		Optional<Answer> oa = this.answerRepository.findById(1);
		assertTrue(oa.isPresent());
		Answer a = oa.get();
		assertEquals(2, a.getQuestion().getId());
	}
}
  • id가 1인 질문이 있는지 확인(true,false), Question_Id가 2인지 확인

질문에서 해당 답변 찾기

@SpringBootTest
class SbbApplicationTests {

	@Autowired
	private AnswerRepository answerRepository;

	@Autowired
	private QuestionRepository questionRepository;

	@Test
	void testJpa() {
		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리포지터리가 findById를 호출하여 Question 객체를 조회하고 나면 DB세션이 끊어지기 때문이다.
  • 그 이후 실행되는 q.getAnswerList()메서드는 세션이 종료되어 오류가 발생
  • 리스트는 객체를 조회 시 가져오지 않고 q.getAnswerList()메서드 호출 당시 가져오기 때문
    • 필요한 시점에 데이터를 가져오는 방식을 Lazy방식이라고 한다.
    • q객체를 조회할때 답변 리스트를 모두 가져오는 것을 Eager방식

@Transactional

위와 같은 문제를 @Transactional을 사용하면 db세션이 유지된다

@SpringBootTest
class SbbApplicationTests {

	@Autowired
	private AnswerRepository answerRepository;

	@Autowired
	private QuestionRepository questionRepository;

	
	@Transactional
	@Test
	void testJpa() {
		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());


	}
}
profile
안녕하세요

0개의 댓글