프로젝트 개요 : Jump to SpringBoot 클론코딩 - 회원제 QnA 게시판 웹
프로젝트 환경 : IntelliJ, SpringBoot, JPA, H2
프로젝트 코드 : https://github.com/smkim9202/sbb
JpaRepository 인터페이스
기본적인 save(), findById 등의 메서드를 제공해준다.
또한 리포지터리 객체의 메서드가 실행될때 JPA가 해당 메서드명을 분석하여 쿼리를 만들고 실행해준다.
'findBy+엔티티의 속성명'과 같은 리포지터리 메서드를 작성하면 해당 속성의 값으로 데이터를 조회 할 수 있는 쿼리를 만들어주기 때문에 인터페이스에 메서드 선언만하고 구현하지 않고도 실행이 된다.
실제로 메서드를 호출 할 때 어떤 쿼리가 실행되는지 콘솔로그로 확인 할 수 있게 설정 할 수 도 있다.
파일명 : 프로젝트명/src/main/resources/application.properties
#JPA
...생략...
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.show_sql=true
객체Repository 생성
- 질문(Question) 엔티티의 리포지토리
파일명 : 프로젝트명/src/main/java/패키지명/QusetionRepository.interface
package com.mysite.sbb;
import org.springframework.data.jpa.repository.JpaRepository;
public interface QuestionRepository extends JpaRepository<Question, Integer> {
}
- 답변(Answer) 엔티티의 리포지토리
파일명 : 프로젝트명/src/main/java/패키지명/AnswerRepository.interface
package com.mysite.sbb;
import org.springframework.data.jpa.repository.JpaRepository;
public interface AnswerRepository extends JpaRepository<Answer, Integer> {
}
리포지터리 테스트 케이스 작성 - 단위 테스트
개발한 기능을 실행해서 테스트 하는 방법
- 자바의 main 메서드 실행
- 웹 애플리케이션의 컨트롤러를 통해 해당 기능 실행
- JUnit 프레임워크로 테스트를 실행
=> 위에 두 방법은 준비하하고 실행하는데 오래 걸리고, 반복 실행하기 어렵고, 여러 테스트를 한번에 실행 할 수 없는데 JUnit으로 테스트를 실행하면 이러한 문제들이 해결된다.
JUnuit : 테스트코드를 작성하고 작성한 테스트코드를 실행하기 위해 사용하는 자바의 프레임워크
테스트 실행시 로컬서버는 중단해야 한다. 만약 로컬서버가 실행 중이라면 오류가 발생한다.
테스트 실행시 JUnit 화면이 나타나고 결과가 뜬다 => 초록색이 표시되면 성공 / 빨간색이 표시되면 실패

데이터 저장하기
save : 테이블에 데이터를 저장하는 메서드
- 질문(Question) 엔티티의 리포지토리 테스트
파일명 : 프로젝트명/src/test/java/패키지명/프로젝트명ApplicationTests.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
//객체를 주입하기 위해 사용하는 애너테이션
//스프링의 DI기능으로 객체를 스프링이 자동으로 생성해준다.
//객체를 주입하는 방식 3가지 : 필드 주입, setter 주입, 생성자 주입
//필드 주입은 순환참조 문제 등의 이유로 권장하지 않는다.
//테스트 코드의 경우 생성자를 통한 객체 주입이 불가능하므로 테스트 코드 작성시에만 사용한다.
private QuestionRepository questionRepository;
@Test
//메서드가 테스트 메서드라고 알려주는 애너테이션
//JUnit으로 실행하면 @Test 애너테이션이 붙은 메서드가 실행된다.
void testJpa() {
//given : q1, q2 객체의 데이터가 주어지고
Question q1 = new Question();
q1.setSubject("sbb가 무엇인가요?");
q1.setContent("sbb에 대해서 알고 싶습니다.");
q1.setCreateDate(LocalDateTime.now());
//id는 엔티티 생성 시 설정한대로 데이터 생성시 속성값이 자동으로 1씩 증가
Question q2 = new Question();
q2.setSubject("스프링부트 모델 질문입니다.");
q2.setContent("id는 자동으로 생성되나요?");
q2.setCreateDate(LocalDateTime.now());
//when : q1, q2 객체를 save(저장)했을 때
this.questionRepository.save(q1); // 첫번째 질문 저장
this.questionRepository.save(q2); // 두번째 질문 저장
}
}
로컬서버 재가동 => H2콘솔 질문테이블 조회 => 저장된 Question 객체의 값이 DB에 저장 되었는지 확인


- 답변(Answer) 엔티티의 리포지토리 테스트
파일명 : 프로젝트명/src/test/java/패키지명/프로젝트명ApplicationTests.java
package com.mysite.sbb;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.time.LocalDateTime;
import java.util.Optional;
import com.mysite.sbb.Question.Question;
import com.mysite.sbb.Question.QuestionRepository;
import com.mysite.sbb.answer.Answer;
import com.mysite.sbb.answer.AnswerRepository;
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;
@Autowired
private AnswerRepository answerRepository;
@Test
void testJpa() {
//given : 질문 id의 값이 2인 데이터에 답변 저장 할 내용이 주어지고
Optional<Question> oq = this.questionRepository.findById(2);
assertTrue(oq.isPresent()); //질문 Id속성의 값이 2가 null인지 아닌지 조회
Question q = oq.get();
Answer a = new Answer();
a.setContent("네 자동으로 생성됩니다.");
a.setQuestion(q); // 어떤 질문의 답변인지 알기위해서 Question 객체가 필요하다.
a.setCreateDate(LocalDateTime.now());
//when : save()로 저장 했을 때
this.answerRepository.save(a);
}
}
데이터 조회하기
findAll : 테이블에 저장된 모든 데이터를 조회하는 메서드
- 질문(Question) 엔티티의 리포지토리 테스트
파일명 : 프로젝트명/src/test/java/패키지명/프로젝트명ApplicationTests.java
package com.mysite.sbb;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
import com.mysite.sbb.Question.Question;
import com.mysite.sbb.Question.QuestionRepository;
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() {
//given : 현재 질문DB에 2개의 질문이 저장되어 있다.
//when : 질문 DB를 findAll(모두조회) 했을 때
List<Question> all = this.questionRepository.findAll();
//then : 어떤 값을 원한다
//assertEquals(기대값, 실제값)
assertEquals(2, all.size());
Question q = all.get(0);
assertEquals("sbb가 무엇인가요?", q.getSubject());
}
}
findById : 테이블에서 id 값으로 데이터 조회하는 메서드
findById의 리턴타입 Question이 아닌 Optional이다.
Optional은 null 처리를 유연하게 처리하기 위해 사용하는 클래스다.isPresent()메서드로 null인지 확인한 후에 get으로 실제 객체값을 얻어야한다.
- 질문(Question) 엔티티의 리포지토리 테스트
파일명 : 프로젝트명/src/test/java/패키지명/프로젝트명ApplicationTests.java
package com.mysite.sbb;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.Optional;
import com.mysite.sbb.Question.Question;
import com.mysite.sbb.Question.QuestionRepository;
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() {
//given : 현재 DB에 id컬럼이 1인 subject컬럼의 데이터 값은 'sbb가 무엇인가요?'다.
//when : 질문 DB에서 id가 1인 데이터를 조회 했을 때
Optional<Question> oq = this.questionRepository.findById(1);
//then
if (oq.isPresent()) { //oq가 null이 아니라면
//isPreent() : null인지 아닌지 확인 하는 메서드
Question q = oq.get(); //null이 아닌것을 확인 후 실제 객체 값을 얻어야 한다.
assertEquals("sbb가 무엇인가요?", q.getSubject());
}
}
}
- 답변(Answer) 엔티티의 리포지토리 테스트
파일명 : 프로젝트명/src/test/java/패키지명/프로젝트명ApplicationTests.java
package com.mysite.sbb;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Optional;
import com.mysite.sbb.answer.Answer;
import com.mysite.sbb.answer.AnswerRepository;
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 AnswerRepository answerRepository;
@Test
void testJpa() {
//when : id속성값이 1인 데이터를 조회 했을 때
Optional<Answer> oa = this.answerRepository.findById(1);
assertTrue(oa.isPresent()); //null값이 아니면
Answer a = oa.get();
//then : 질문id가 2를 원한다
assertEquals(2, a.getQuestion().getId());
}
}
- 답변(Answer)에 연결된 질문(Question) 찾기 : Answer엔티티의 question 속성 이용하기 - getQuestion()
파일명 : 프로젝트명/src/test/java/패키지명/프로젝트명ApplicationTests.java
package com.mysite.sbb;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import java.util.Optional;
import com.mysite.sbb.Question.Question;
import com.mysite.sbb.Question.QuestionRepository;
import com.mysite.sbb.answer.Answer;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
@SpringBootTest
class SbbApplicationTests {
@Autowired
private QuestionRepository questionRepository;
@Transactional //메서드 종료될 때까지 DB세션을 유지하는 애너테이션
@Test
void testJpa() {
//given : Question Id의 값이 2인 데이터가 주어지고
Optional<Question> oq = this.questionRepository.findById(2);
assertTrue(oq.isPresent()); //Id값의 2인 데이터가 null이 아니라면
Question q = oq.get();
//when : getAnswerList() 답변리스트를 가져 왔을 때
List<Answer> answerList = q.getAnswerList();
//then
assertEquals(1, answerList.size());
assertEquals("네 자동으로 생성됩니다.", answerList.get(0).getContent());
}
}
@Transactional 애너테이션 없이 실행한다면?
org.hibernate.LazyInitializationException 오류 발생
findById() 호출하여 Question 객체 조회 후 DB세션 끊어짐 => DB세션이 종료된 후 실행하는 q.getAnswerList();에서오류 발생한다.
답변데이터 리스트는 q 객체를 조회 할 때 가져오지 않고, q.getAnswerList() 메서드를 호출하는 시점에서 가져오기 때문이다.
필요한 시점에 데이터를 가져오는 방식 : Lazy 방식 / q 객체 조회 시 답변 리스트를 모두 가져오는 방식 : Eager 방식
@OneToMany, @ManyToOne 애너테이션의 옵션으로 fetch=FetchType.LAZY 또는 fetch=FetchType.EAGER 처럼 지정 가능
이 문제는 테스트 코드에서만 발생하는데, 실제 서버에서는 JPA 프로그램 실행 시 DB세션이 종료되지 않기 때문에 LazyInitializationException이 발생하지 않는다.
테스트 코드 수행 시 LazyInitializationException 오류 방지하는 간단한 방법 : @Transactional 애너테이션 사용 - 메서드 종료 될 때까지 DB세션 유지한다.
findBy+엔티티속성명 : 테이블에서 속성명의 값으로 데이터 조회하는 메서드
Id가 아닌 엔티티속성명(컬럼명)들은 메서드를 기본적으로 제공해주지 않는다.
특정 속성명의 값으로 데이터를 조회하고 싶으면 리포지터리를 변경해야한다.=> '객체타입 findBy+속성명(속성명타입 속성명);'를 추가해주자단, 중복된 값(속성값이 똑같은 경우)이 있을 시 테스트가 실패한다.
- 질문(Question) 엔티티의 리포지토리 테스트 - findBySubject()
파일명 : 프로젝트명/src/main/java/패키지명/QusetionRepository.java
package com.mysite.sbb;
import org.springframework.data.jpa.repository.JpaRepository;
public interface QuestionRepository extends JpaRepository<Question, Integer> {
Question findBySubject(String subject);
//findBySbject() : 제목으로 테이블 데이터 조회하기
}
파일명 : 프로젝트명/src/test/java/패키지명/프로젝트명ApplicationTests.java
package com.mysite.sbb;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.mysite.sbb.Question.Question;
import com.mysite.sbb.Question.QuestionRepository;
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() {
//when : findBySubject()로 제목이 "sbb가 무엇인가요?"인 테이블 데이터를 조회 했을 때
Question q = this.questionRepository.findBySubject("sbb가 무엇인가요?");
//then
assertEquals(1, q.getId());
}
}
findBy+엔티티속성명1And엔티티속성명2 : 두 개의 속성을 And 조건으로 조회하는 메서드
쿼리문의 where 조건을 결정하는 역할을 한다. 여러 컬럼을 And로 검색
- 질문(Question) 엔티티의 리포지토리 테스트 - findBySubjectAndContent()
파일명 : 프로젝트명/src/main/java/패키지명/QusetionRepository.java
package com.mysite.sbb;
import org.springframework.data.jpa.repository.JpaRepository;
public interface QuestionRepository extends JpaRepository<Question, Integer> {
...생략...
Question findBySubjectAndContent(String subject, String content);
//findBySubjectAndContent() : 제목과 내용을 함께 조회
}
파일명 : 프로젝트명/src/test/java/패키지명/프로젝트명ApplicationTests.java
package com.mysite.sbb;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.mysite.sbb.Question.Question;
import com.mysite.sbb.Question.QuestionRepository;
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() {
//when : findBySubjectAndContent()로 제목과 내용을 함께 조회하면
Question q = this.questionRepository.findBySubjectAndContent(
"sbb가 무엇인가요?", "sbb에 대해서 알고 싶습니다.");
//then
assertEquals(1, q.getId());
}
}
And외에 where 조건을 결정하는 항목들
항목 | 예제 | 설명 |
And | findBySubjectAndContent(String subject, String content) | 여러 컬럼을 and 로 검색 |
Or | findBySubjectOrContent(String subject, String content) | 여러 컬럼을 or 로 검색 |
Between | findByCreateDateBetween(LocalDateTime fromDate, LocalDateTime toDate) | 컬럼을 between으로 검색 |
LessThan | findByIdLessThan(Integer id) | 작은 항목 검색 |
GreaterThanEqual | findByIdGraterThanEqual(Integer id) | 크거나 같은 항목 검색 |
Like | findBySubjectLike(String subject) | like 검색 |
In | findBySubjectIn(String[] subjects) | 여러 값중에 하나인 항목 검색 |
OrderBy | findBySubjectOrderByCreateDateAsc(String subject) | 검색 결과를 정렬하여 전달 |
응답 결과가 여러건인 경우에는 리포지터리 메서드의 리턴 타입을 엔티티명이 아닌 List<엔티티명>으로 해야한다.
쿼리 생성 규칙 공식문서
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation
Spring Data JPA - Reference Documentation
Example 109. Using @Transactional at query methods @Transactional(readOnly = true) interface UserRepository extends JpaRepository { List findByLastname(String lastname); @Modifying @Transactional @Query("delete from User u where u.active = false") void del
docs.spring.io
findBy+엔티티속성명Like : 속성명에 특정문자열이 포함되어 있는 데이터 조회하는 메서드
Like : 특정문자열 포함되어 있는 데이터 조회
- 문자열% : "문자열"로 시작하는 문자열
- %문자열 : "문자열"로 끝나는 문자열
- %문자열% : "문자열"을 포함하는 문자열
- 질문(Question) 엔티티의 리포지토리 테스트 - findBySubjectAndContent()
파일명 : 프로젝트명/src/main/java/패키지명/QusetionRepository.java
package com.mysite.sbb;
import org.springframework.data.jpa.repository.JpaRepository;
public interface QuestionRepository extends JpaRepository<Question, Integer> {
...생략...
List<Question> findBySubjectLike(String subject);
//findBySubjectLike() : 제목에 특정문자열이 포함되어 있는 데이터 조회
}
파일명 : 프로젝트명/src/test/java/패키지명/프로젝트명ApplicationTests.java
package com.mysite.sbb;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
import com.mysite.sbb.Question.Question;
import com.mysite.sbb.Question.QuestionRepository;
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() {
//when : findBySubjectLike("sbb%")로 제목이 sbb로 시작하는 데이터 조회 할 때
List<Question> qList = this.questionRepository.findBySubjectLike("sbb%");
//then
Question q = qList.get(0);
assertEquals("sbb가 무엇인가요?", q.getSubject());
}
}
데이터 수정하기
update문이 실행된다.
save
파일명 : 프로젝트명/src/test/java/패키지명/프로젝트명ApplicationTests.java
package com.mysite.sbb;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Optional;
import com.mysite.sbb.Question.Question;
import com.mysite.sbb.Question.QuestionRepository;
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() {
//given : Id가 1인 데이터가 주어졌을 때 수정된 값이 주어지고
Optional<Question> oq = this.questionRepository.findById(1);
assertTrue(oq.isPresent()); //Id값의 null이 아니면
Question q = oq.get();
q.setSubject("수정된 제목"); //제목을 수정해준 값을
//when : save()로 저장했을 때
this.questionRepository.save(q);
}
}
데이터 삭제하기
delete
파일명 : 프로젝트명/src/test/java/패키지명/프로젝트명ApplicationTests.java
package com.mysite.sbb;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Optional;
import com.mysite.sbb.Question.Question;
import com.mysite.sbb.Question.QuestionRepository;
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() {
//given : Id속성의 값이 1인 데이터 객체가 주이지고
assertEquals(2, this.questionRepository.count());
//리포지터리의 count() : 해당 리포지터리의 총 데이터건수 리턴
Optional<Question> oq = this.questionRepository.findById(1);
assertTrue(oq.isPresent()); //Id값이 1인 데이터가 null인지 아닌지 조회
Question q = oq.get();
//when : delete로 q객체를 삭제 했을 때
this.questionRepository.delete(q);
//then
assertEquals(1, this.questionRepository.count());
}
}
'프로젝트 > clone coding' 카테고리의 다른 글
[Jump to SpringBoot : SBB] 스프링부트 windows cmd창으로 빌드하고 실행 (0) | 2022.04.21 |
---|---|
[Jump to SpringBoot : SBB] 서비스와 DTO (0) | 2022.04.20 |
[Jump to SpringBoot : SBB] thymeleaf 템플릿 엔진(+ROOT URL) (0) | 2022.04.19 |
[Jump to SpringBoot : SBB] SpringBoot 기본 요소(Controller, JPA, Entity, Repository) (0) | 2022.04.19 |
[Jump to SpringBoot : SBB] IntelliJ에서 SpringBoot 사용하기 (0) | 2022.04.18 |
프로젝트 개요 : Jump to SpringBoot 클론코딩 - 회원제 QnA 게시판 웹
프로젝트 환경 : IntelliJ, SpringBoot, JPA, H2
프로젝트 코드 : https://github.com/smkim9202/sbb
JpaRepository 인터페이스
기본적인 save(), findById 등의 메서드를 제공해준다.
또한 리포지터리 객체의 메서드가 실행될때 JPA가 해당 메서드명을 분석하여 쿼리를 만들고 실행해준다.
'findBy+엔티티의 속성명'과 같은 리포지터리 메서드를 작성하면 해당 속성의 값으로 데이터를 조회 할 수 있는 쿼리를 만들어주기 때문에 인터페이스에 메서드 선언만하고 구현하지 않고도 실행이 된다.
실제로 메서드를 호출 할 때 어떤 쿼리가 실행되는지 콘솔로그로 확인 할 수 있게 설정 할 수 도 있다.
파일명 : 프로젝트명/src/main/resources/application.properties
#JPA
...생략...
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.show_sql=true
객체Repository 생성
- 질문(Question) 엔티티의 리포지토리
파일명 : 프로젝트명/src/main/java/패키지명/QusetionRepository.interface
package com.mysite.sbb;
import org.springframework.data.jpa.repository.JpaRepository;
public interface QuestionRepository extends JpaRepository<Question, Integer> {
}
- 답변(Answer) 엔티티의 리포지토리
파일명 : 프로젝트명/src/main/java/패키지명/AnswerRepository.interface
package com.mysite.sbb;
import org.springframework.data.jpa.repository.JpaRepository;
public interface AnswerRepository extends JpaRepository<Answer, Integer> {
}
리포지터리 테스트 케이스 작성 - 단위 테스트
개발한 기능을 실행해서 테스트 하는 방법
- 자바의 main 메서드 실행
- 웹 애플리케이션의 컨트롤러를 통해 해당 기능 실행
- JUnit 프레임워크로 테스트를 실행
=> 위에 두 방법은 준비하하고 실행하는데 오래 걸리고, 반복 실행하기 어렵고, 여러 테스트를 한번에 실행 할 수 없는데 JUnit으로 테스트를 실행하면 이러한 문제들이 해결된다.
JUnuit : 테스트코드를 작성하고 작성한 테스트코드를 실행하기 위해 사용하는 자바의 프레임워크
테스트 실행시 로컬서버는 중단해야 한다. 만약 로컬서버가 실행 중이라면 오류가 발생한다.
테스트 실행시 JUnit 화면이 나타나고 결과가 뜬다 => 초록색이 표시되면 성공 / 빨간색이 표시되면 실패

데이터 저장하기
save : 테이블에 데이터를 저장하는 메서드
- 질문(Question) 엔티티의 리포지토리 테스트
파일명 : 프로젝트명/src/test/java/패키지명/프로젝트명ApplicationTests.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
//객체를 주입하기 위해 사용하는 애너테이션
//스프링의 DI기능으로 객체를 스프링이 자동으로 생성해준다.
//객체를 주입하는 방식 3가지 : 필드 주입, setter 주입, 생성자 주입
//필드 주입은 순환참조 문제 등의 이유로 권장하지 않는다.
//테스트 코드의 경우 생성자를 통한 객체 주입이 불가능하므로 테스트 코드 작성시에만 사용한다.
private QuestionRepository questionRepository;
@Test
//메서드가 테스트 메서드라고 알려주는 애너테이션
//JUnit으로 실행하면 @Test 애너테이션이 붙은 메서드가 실행된다.
void testJpa() {
//given : q1, q2 객체의 데이터가 주어지고
Question q1 = new Question();
q1.setSubject("sbb가 무엇인가요?");
q1.setContent("sbb에 대해서 알고 싶습니다.");
q1.setCreateDate(LocalDateTime.now());
//id는 엔티티 생성 시 설정한대로 데이터 생성시 속성값이 자동으로 1씩 증가
Question q2 = new Question();
q2.setSubject("스프링부트 모델 질문입니다.");
q2.setContent("id는 자동으로 생성되나요?");
q2.setCreateDate(LocalDateTime.now());
//when : q1, q2 객체를 save(저장)했을 때
this.questionRepository.save(q1); // 첫번째 질문 저장
this.questionRepository.save(q2); // 두번째 질문 저장
}
}
로컬서버 재가동 => H2콘솔 질문테이블 조회 => 저장된 Question 객체의 값이 DB에 저장 되었는지 확인


- 답변(Answer) 엔티티의 리포지토리 테스트
파일명 : 프로젝트명/src/test/java/패키지명/프로젝트명ApplicationTests.java
package com.mysite.sbb;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.time.LocalDateTime;
import java.util.Optional;
import com.mysite.sbb.Question.Question;
import com.mysite.sbb.Question.QuestionRepository;
import com.mysite.sbb.answer.Answer;
import com.mysite.sbb.answer.AnswerRepository;
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;
@Autowired
private AnswerRepository answerRepository;
@Test
void testJpa() {
//given : 질문 id의 값이 2인 데이터에 답변 저장 할 내용이 주어지고
Optional<Question> oq = this.questionRepository.findById(2);
assertTrue(oq.isPresent()); //질문 Id속성의 값이 2가 null인지 아닌지 조회
Question q = oq.get();
Answer a = new Answer();
a.setContent("네 자동으로 생성됩니다.");
a.setQuestion(q); // 어떤 질문의 답변인지 알기위해서 Question 객체가 필요하다.
a.setCreateDate(LocalDateTime.now());
//when : save()로 저장 했을 때
this.answerRepository.save(a);
}
}
데이터 조회하기
findAll : 테이블에 저장된 모든 데이터를 조회하는 메서드
- 질문(Question) 엔티티의 리포지토리 테스트
파일명 : 프로젝트명/src/test/java/패키지명/프로젝트명ApplicationTests.java
package com.mysite.sbb;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
import com.mysite.sbb.Question.Question;
import com.mysite.sbb.Question.QuestionRepository;
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() {
//given : 현재 질문DB에 2개의 질문이 저장되어 있다.
//when : 질문 DB를 findAll(모두조회) 했을 때
List<Question> all = this.questionRepository.findAll();
//then : 어떤 값을 원한다
//assertEquals(기대값, 실제값)
assertEquals(2, all.size());
Question q = all.get(0);
assertEquals("sbb가 무엇인가요?", q.getSubject());
}
}
findById : 테이블에서 id 값으로 데이터 조회하는 메서드
findById의 리턴타입 Question이 아닌 Optional이다.
Optional은 null 처리를 유연하게 처리하기 위해 사용하는 클래스다.isPresent()메서드로 null인지 확인한 후에 get으로 실제 객체값을 얻어야한다.
- 질문(Question) 엔티티의 리포지토리 테스트
파일명 : 프로젝트명/src/test/java/패키지명/프로젝트명ApplicationTests.java
package com.mysite.sbb;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.Optional;
import com.mysite.sbb.Question.Question;
import com.mysite.sbb.Question.QuestionRepository;
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() {
//given : 현재 DB에 id컬럼이 1인 subject컬럼의 데이터 값은 'sbb가 무엇인가요?'다.
//when : 질문 DB에서 id가 1인 데이터를 조회 했을 때
Optional<Question> oq = this.questionRepository.findById(1);
//then
if (oq.isPresent()) { //oq가 null이 아니라면
//isPreent() : null인지 아닌지 확인 하는 메서드
Question q = oq.get(); //null이 아닌것을 확인 후 실제 객체 값을 얻어야 한다.
assertEquals("sbb가 무엇인가요?", q.getSubject());
}
}
}
- 답변(Answer) 엔티티의 리포지토리 테스트
파일명 : 프로젝트명/src/test/java/패키지명/프로젝트명ApplicationTests.java
package com.mysite.sbb;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Optional;
import com.mysite.sbb.answer.Answer;
import com.mysite.sbb.answer.AnswerRepository;
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 AnswerRepository answerRepository;
@Test
void testJpa() {
//when : id속성값이 1인 데이터를 조회 했을 때
Optional<Answer> oa = this.answerRepository.findById(1);
assertTrue(oa.isPresent()); //null값이 아니면
Answer a = oa.get();
//then : 질문id가 2를 원한다
assertEquals(2, a.getQuestion().getId());
}
}
- 답변(Answer)에 연결된 질문(Question) 찾기 : Answer엔티티의 question 속성 이용하기 - getQuestion()
파일명 : 프로젝트명/src/test/java/패키지명/프로젝트명ApplicationTests.java
package com.mysite.sbb;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import java.util.Optional;
import com.mysite.sbb.Question.Question;
import com.mysite.sbb.Question.QuestionRepository;
import com.mysite.sbb.answer.Answer;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
@SpringBootTest
class SbbApplicationTests {
@Autowired
private QuestionRepository questionRepository;
@Transactional //메서드 종료될 때까지 DB세션을 유지하는 애너테이션
@Test
void testJpa() {
//given : Question Id의 값이 2인 데이터가 주어지고
Optional<Question> oq = this.questionRepository.findById(2);
assertTrue(oq.isPresent()); //Id값의 2인 데이터가 null이 아니라면
Question q = oq.get();
//when : getAnswerList() 답변리스트를 가져 왔을 때
List<Answer> answerList = q.getAnswerList();
//then
assertEquals(1, answerList.size());
assertEquals("네 자동으로 생성됩니다.", answerList.get(0).getContent());
}
}
@Transactional 애너테이션 없이 실행한다면?
org.hibernate.LazyInitializationException 오류 발생
findById() 호출하여 Question 객체 조회 후 DB세션 끊어짐 => DB세션이 종료된 후 실행하는 q.getAnswerList();에서오류 발생한다.
답변데이터 리스트는 q 객체를 조회 할 때 가져오지 않고, q.getAnswerList() 메서드를 호출하는 시점에서 가져오기 때문이다.
필요한 시점에 데이터를 가져오는 방식 : Lazy 방식 / q 객체 조회 시 답변 리스트를 모두 가져오는 방식 : Eager 방식
@OneToMany, @ManyToOne 애너테이션의 옵션으로 fetch=FetchType.LAZY 또는 fetch=FetchType.EAGER 처럼 지정 가능
이 문제는 테스트 코드에서만 발생하는데, 실제 서버에서는 JPA 프로그램 실행 시 DB세션이 종료되지 않기 때문에 LazyInitializationException이 발생하지 않는다.
테스트 코드 수행 시 LazyInitializationException 오류 방지하는 간단한 방법 : @Transactional 애너테이션 사용 - 메서드 종료 될 때까지 DB세션 유지한다.
findBy+엔티티속성명 : 테이블에서 속성명의 값으로 데이터 조회하는 메서드
Id가 아닌 엔티티속성명(컬럼명)들은 메서드를 기본적으로 제공해주지 않는다.
특정 속성명의 값으로 데이터를 조회하고 싶으면 리포지터리를 변경해야한다.=> '객체타입 findBy+속성명(속성명타입 속성명);'를 추가해주자단, 중복된 값(속성값이 똑같은 경우)이 있을 시 테스트가 실패한다.
- 질문(Question) 엔티티의 리포지토리 테스트 - findBySubject()
파일명 : 프로젝트명/src/main/java/패키지명/QusetionRepository.java
package com.mysite.sbb;
import org.springframework.data.jpa.repository.JpaRepository;
public interface QuestionRepository extends JpaRepository<Question, Integer> {
Question findBySubject(String subject);
//findBySbject() : 제목으로 테이블 데이터 조회하기
}
파일명 : 프로젝트명/src/test/java/패키지명/프로젝트명ApplicationTests.java
package com.mysite.sbb;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.mysite.sbb.Question.Question;
import com.mysite.sbb.Question.QuestionRepository;
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() {
//when : findBySubject()로 제목이 "sbb가 무엇인가요?"인 테이블 데이터를 조회 했을 때
Question q = this.questionRepository.findBySubject("sbb가 무엇인가요?");
//then
assertEquals(1, q.getId());
}
}
findBy+엔티티속성명1And엔티티속성명2 : 두 개의 속성을 And 조건으로 조회하는 메서드
쿼리문의 where 조건을 결정하는 역할을 한다. 여러 컬럼을 And로 검색
- 질문(Question) 엔티티의 리포지토리 테스트 - findBySubjectAndContent()
파일명 : 프로젝트명/src/main/java/패키지명/QusetionRepository.java
package com.mysite.sbb;
import org.springframework.data.jpa.repository.JpaRepository;
public interface QuestionRepository extends JpaRepository<Question, Integer> {
...생략...
Question findBySubjectAndContent(String subject, String content);
//findBySubjectAndContent() : 제목과 내용을 함께 조회
}
파일명 : 프로젝트명/src/test/java/패키지명/프로젝트명ApplicationTests.java
package com.mysite.sbb;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.mysite.sbb.Question.Question;
import com.mysite.sbb.Question.QuestionRepository;
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() {
//when : findBySubjectAndContent()로 제목과 내용을 함께 조회하면
Question q = this.questionRepository.findBySubjectAndContent(
"sbb가 무엇인가요?", "sbb에 대해서 알고 싶습니다.");
//then
assertEquals(1, q.getId());
}
}
And외에 where 조건을 결정하는 항목들
항목 | 예제 | 설명 |
And | findBySubjectAndContent(String subject, String content) | 여러 컬럼을 and 로 검색 |
Or | findBySubjectOrContent(String subject, String content) | 여러 컬럼을 or 로 검색 |
Between | findByCreateDateBetween(LocalDateTime fromDate, LocalDateTime toDate) | 컬럼을 between으로 검색 |
LessThan | findByIdLessThan(Integer id) | 작은 항목 검색 |
GreaterThanEqual | findByIdGraterThanEqual(Integer id) | 크거나 같은 항목 검색 |
Like | findBySubjectLike(String subject) | like 검색 |
In | findBySubjectIn(String[] subjects) | 여러 값중에 하나인 항목 검색 |
OrderBy | findBySubjectOrderByCreateDateAsc(String subject) | 검색 결과를 정렬하여 전달 |
응답 결과가 여러건인 경우에는 리포지터리 메서드의 리턴 타입을 엔티티명이 아닌 List<엔티티명>으로 해야한다.
쿼리 생성 규칙 공식문서
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation
Spring Data JPA - Reference Documentation
Example 109. Using @Transactional at query methods @Transactional(readOnly = true) interface UserRepository extends JpaRepository { List findByLastname(String lastname); @Modifying @Transactional @Query("delete from User u where u.active = false") void del
docs.spring.io
findBy+엔티티속성명Like : 속성명에 특정문자열이 포함되어 있는 데이터 조회하는 메서드
Like : 특정문자열 포함되어 있는 데이터 조회
- 문자열% : "문자열"로 시작하는 문자열
- %문자열 : "문자열"로 끝나는 문자열
- %문자열% : "문자열"을 포함하는 문자열
- 질문(Question) 엔티티의 리포지토리 테스트 - findBySubjectAndContent()
파일명 : 프로젝트명/src/main/java/패키지명/QusetionRepository.java
package com.mysite.sbb;
import org.springframework.data.jpa.repository.JpaRepository;
public interface QuestionRepository extends JpaRepository<Question, Integer> {
...생략...
List<Question> findBySubjectLike(String subject);
//findBySubjectLike() : 제목에 특정문자열이 포함되어 있는 데이터 조회
}
파일명 : 프로젝트명/src/test/java/패키지명/프로젝트명ApplicationTests.java
package com.mysite.sbb;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
import com.mysite.sbb.Question.Question;
import com.mysite.sbb.Question.QuestionRepository;
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() {
//when : findBySubjectLike("sbb%")로 제목이 sbb로 시작하는 데이터 조회 할 때
List<Question> qList = this.questionRepository.findBySubjectLike("sbb%");
//then
Question q = qList.get(0);
assertEquals("sbb가 무엇인가요?", q.getSubject());
}
}
데이터 수정하기
update문이 실행된다.
save
파일명 : 프로젝트명/src/test/java/패키지명/프로젝트명ApplicationTests.java
package com.mysite.sbb;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Optional;
import com.mysite.sbb.Question.Question;
import com.mysite.sbb.Question.QuestionRepository;
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() {
//given : Id가 1인 데이터가 주어졌을 때 수정된 값이 주어지고
Optional<Question> oq = this.questionRepository.findById(1);
assertTrue(oq.isPresent()); //Id값의 null이 아니면
Question q = oq.get();
q.setSubject("수정된 제목"); //제목을 수정해준 값을
//when : save()로 저장했을 때
this.questionRepository.save(q);
}
}
데이터 삭제하기
delete
파일명 : 프로젝트명/src/test/java/패키지명/프로젝트명ApplicationTests.java
package com.mysite.sbb;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Optional;
import com.mysite.sbb.Question.Question;
import com.mysite.sbb.Question.QuestionRepository;
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() {
//given : Id속성의 값이 1인 데이터 객체가 주이지고
assertEquals(2, this.questionRepository.count());
//리포지터리의 count() : 해당 리포지터리의 총 데이터건수 리턴
Optional<Question> oq = this.questionRepository.findById(1);
assertTrue(oq.isPresent()); //Id값이 1인 데이터가 null인지 아닌지 조회
Question q = oq.get();
//when : delete로 q객체를 삭제 했을 때
this.questionRepository.delete(q);
//then
assertEquals(1, this.questionRepository.count());
}
}
'프로젝트 > clone coding' 카테고리의 다른 글
[Jump to SpringBoot : SBB] 스프링부트 windows cmd창으로 빌드하고 실행 (0) | 2022.04.21 |
---|---|
[Jump to SpringBoot : SBB] 서비스와 DTO (0) | 2022.04.20 |
[Jump to SpringBoot : SBB] thymeleaf 템플릿 엔진(+ROOT URL) (0) | 2022.04.19 |
[Jump to SpringBoot : SBB] SpringBoot 기본 요소(Controller, JPA, Entity, Repository) (0) | 2022.04.19 |
[Jump to SpringBoot : SBB] IntelliJ에서 SpringBoot 사용하기 (0) | 2022.04.18 |