반응형
- 이전 포스팅 확인하기
https://luvris2.tistory.com/366
Spring Boot - H2 데이터베이스 - JPA 엔티티 설정, 리파지토리 설정 (2/3)
JPA 엔티티 설정 (Entity) Entity란? 사전적 의미로는 개체, 실체의 의미 데이터베이스 논리적 구성요소 유형, 무형의 개체로 현실세계에서 사람이 생각하는 개념이나 정보의 단위 JPA의 엔티티는 쉽
luvris2.tistory.com
해당 포스팅은 'JUnit Test'를 이용하여 DB CRUD 테스트를 진행합니다.
테스트 설정
테스트 파일 생성
- 테스트를 위해서는 테스트 파일이 필요
- 테스트 파일은 'src/test/java' 경로에서 생성
- 'src/main/java'의 패키지와 동일한 이름으로 생성
- 예시) 테스트를 위해 패키지명 설정
- src/main/java : com.example.h2dbtest
- src/test/java : com.example.h2dbtest
- 예시) 테스트를 위해 패키지명 설정
- 원하는 임의의 이름으로 생성
- 포스팅에서는 프로젝트의 일관성을 위해 메인 자바 파일 뒤에 Test를 붙임
- 물론 파일 이름은 중요하지 않으므로 자유롭게 작성
테스트 파일 설정
테스트 파일 선언
- 생선한 파일을 열고 클래스 이름 위에 @SpringBootTest 입력
- @SpringBootTest : 테스트를 하기 위한 클래스 선언
@SpringBootTest
테스트용 생성자 설정 (Setter)
@Autowired
- 객체를 주입하기 위해 사용하는 스프링의 애너테이션
- 리파지토리 클래스의 객체를 스프링이 자동으로 생성
- 객체 주입 방식의 종류 : Setter / 생성자 / @Autowired
- 순환참조 문제와 같은 이유로 생성자를 통한 객체 주입 방식 권장
- 테스트 코드에서는 생성자를 통한 객체 주입이 불가능하므로 @Autowired 사용
- 실제 코드에서는 생성자 사용
// H2DbTestApplication.java
// 해당 리파지토리 클래스의 객체 자동 생성
@Autowired
// private Repository repository;
// 공지사항 리파지토리
private H2dbNoticeRepository h2dbNoticeRepository;
// 댓글 리파지토리
private H2dbReplyRepository h2dbReplyRepository;
전체 코드
// H2DbTestApplication.java
@SpringBootTest
class H2DbTestApplicationTest {
@Autowired
private H2dbNoticeRepository h2dbNoticeRepository;
private H2dbReplyRepository h2dbReplyRepository;
}
데이터베이스 제어하기 (CRUD)
- 해당 코드는 테스트 클래스에서 진행합니다.
- 포스팅에서는 H2DbTestApplicationTest.java 파일로 진행하였습니다.
Create
- 엔티티 객체화
- H2dbNotice h= new H2dbNotice();
- 객체화된 엔티티의 값 입력 (@Autowired, =Setter)
- h.setColumn("Value");
- 값이 입력된 객체 리파지토리에 저장 (DB에 데이터 생성)
- this.repo.save(h);
// H2DbTestApplication.java
// 데이터 생성하기
void testJpa_create() {
// 공지사항 엔티티 객체 'noticeCreate1' 생성
H2dbNotice noticeCreate1 = new H2dbNotice();
// 공지사항 테이블의 컬럼 값 입력
// 해당 코드는 SQL문의 INSERT와 같은 의미를 지님
// insert into H2db_notice (subject, content, createdate)
// values ("첫번째 공지사항입니다.", "첫번째 공지사항이 작성되었습니다.", now());
noticeCreate1.setSubject("첫번째 공지사항입니다.");
noticeCreate1.setContent("첫번째 공지사항이 작성되었습니다.");
noticeCreate1.setCreateDate(LocalDateTime.now());
// 엔티티 객체에서 설정한 데이터 리파지토리에 저장 (= DB에 Insert문 실행)
this.h2dbNoticeRepository.save(noticeCreate1);
// 첫번째 공지사항 저장
// 공지사항 엔티티 객체 'noticeCreate2' 생성
// 위의 주석과 같음
H2dbNotice noticeCreate2 = new H2dbNotice();
noticeCreate2.setSubject("두번째 게시판 공지");
noticeCreate2.setContent("두번째 글 입니다.");
noticeCreate2.setCreateDate(LocalDateTime.now());
this.h2dbNoticeRepository.save(noticeCreate2);
// 두번째 공지사항 저장
}
Read
- 데이터 확인을 위해 JUnit의 assertEquals 사용
- assertEquals(기댓값, 실제값)
- 기댓값과 실제값이 같으면 테스트 성공, 다르면 실패
- 해당 메소드는 테스트 전용으로 사용
- 리파지토리에 n번 ID의 데이터 옵셔널 타입으로 반환
- Optional<H2dbNotice> op = this.repo.findById(n);
- 데이터 null 체크
- op.isPresent() : 데이터 존재 1 (if문 실행), null 0
- 옵셔널 타입으로 반환된 데이터 엔티티 객체에 저장
- H2dbNotice h = op.get();
- 데이터 확인
- assertEquals("기댓값", "실제값");
- 맞으면 성공(테스트시 녹색 결과), 틀리면 실패(테스트시 빨간색 결과)
- assertEquals("기댓값", "실제값");
findAll (모든 데이터 조회)
// H2DbTestApplication.java
// !!! 데이터 확인을 위해 JUnit의 assertEquals 사용 !!!
// !!! assertEquals(기댓값, 실제값) !!!
// 데이터 조회하기 findById
void testJpa_read_findById() {
// 조회 findById
// findById는 Optional 타입으로 값 반환
// isPresent : 데이터 객체의 Null 확인
Optional<H2dbNotice> noticeData_findById = this.h2dbNoticeRepository.findById(1);
if(noticeData_findById.isPresent()) {
H2dbNotice noticeFindById = noticeData_findById.get();
assertEquals("두번째 게시판 공지", noticeFindById.getSubject());
}
}
findById (특정 ID 데이터 조회)
// H2DbTestApplication.java
// !!! 데이터 확인을 위해 JUnit의 assertEquals 사용 !!!
// !!! assertEquals(기댓값, 실제값) !!!
// 데이터 조회하기 findById
void testJpa_read_findById() {
// 조회 findById
// findById는 Optional 타입으로 값 반환
// isPresent : 데이터 객체의 Null 확인
Optional<H2dbNotice> noticeData_findById = this.h2dbNoticeRepository.findById(2);
if(noticeData_findById.isPresent()) {
H2dbNotice noticeFindById = noticeData_findById.get();
assertEquals("두번째 게시판 공지", noticeFindById.getSubject());
}
}
findBy + Column (특정 컬럼 데이터 조회)
// H2DbTestApplication.java
// !!! 데이터 확인을 위해 JUnit의 assertEquals 사용 !!!
// !!! assertEquals(기댓값, 실제값) !!!
// 데이터 조회하기 findBySubject
void testJpa_read_findBySubject() {
// 조회 findBy + Column
// findBy + 엔티티 속성명(컬럼) 메소드 : 해당 속성의 값으로 데이터 조회
// 리파지토리에 해당 메소드를 정의해야 사용 가능
H2dbNotice noticeData_findBySubject = this.h2dbNoticeRepository.findBySubject("첫번째 공지사항입니다.");
assertEquals(1, noticeData_findBySubject.getId());
findBy + Column + Like (특정 컬럼의 문자열이 포함되는 데이터 검색)
// H2DbTestApplication.java
// !!! 데이터 확인을 위해 JUnit의 assertEquals 사용 !!!
// !!! assertEquals(기댓값, 실제값) !!!
// 데이터 조회하기 findByLike
void testJpa_read_findByLike() {
// 특정 문자열 조회 findBy + Column + Like
// findBy + 엔티티 속성명(컬럼) Like : 특정 문자열로 해당 속성의 데이터 조회
// 리파지토리에 해당 메소드를 정의해야 사용 가능
// %검색어 : 검색어로 끝나는 문자열
// 검색어% : 검색어로 시작하는 문자열
// %검색어% : 검색어가 포함되는 문자열
List<H2dbNotice> noticeData_findLike = h2dbNoticeRepository.findBySubjectLike("%공지%");
H2dbNotice noticeFindLike = noticeData_findLike.get(0);
assertEquals("첫번째 공지사항입니다.", noticeFindLike.getSubject());
}
Update
- 리파지토리에 n번 ID의 데이터 옵셔널 타입으로 반환
- Optional<H2dbNotice> op = this.repo.findById(1);
- 데이터 null 체크
- assertTrue(op.isPresent());
- isPresent() : 데이터가 존재하면 1 반환
- assertTrue(값) : 값이 참(true/1)이면 테스트 성공
- assertTrue(op.isPresent());
- 옵셔널 타입으로 반환된 데이터 엔티티 객체에 저장
- H2dbNotice h = op.get();
- 객체화된 엔티티의 값이 있는 ID에 수정 할 값 입력
- h.setColumn("update Value");
- 값이 입력된 객체 리파지토리에 저장 (DB에 데이터 생성)
- this.repo.save(h);
// H2DbTestApplication.java
// 데이터 수정하기
void testJpa_update() {
// 조회와 비슷하지만 save를 이용하여 해당 속성을 저장
// 데이터베이스에서는 update문을 실행
Optional<H2dbNotice> noticeData_update = this.h2dbNoticeRepository.findById(1);
assertTrue(noticeData_update.isPresent());
H2dbNotice noticeUpdate = noticeData_update.get();
noticeUpdate.setSubject("수정된 제목");
this.h2dbNoticeRepository.save(noticeUpdate);
Delete
- 데이터 확인
- assertEquals("기댓값", "실제값");
- 리파지토리에 n번 ID의 데이터 옵셔널 타입으로 반환
- Optional<H2dbNotice> op = this.repo.findById(1);
- 데이터 null 체크
- assertTrue(op.isPresent());
- isPresent() : 데이터가 존재하면 1 반환
- assertTrue(값) : 값이 참(true/1)이면 테스트 성공
- assertTrue(op.isPresent());
- 옵셔널 타입으로 반환된 데이터 엔티티 객체에 저장
- H2dbNotice h = op.get();
- 값이 입력된 객체 리파지토리에서 삭제 (DB에 데이터 삭제)
- this.repo.delete(h);
- 삭제되었는지 데이터 확인
- assertEquals("기댓값", "실제값");
// H2DbTestApplication.java
// 데이터 삭제하기
void testJpa_delete() {
// 리파지토리.delete(데이터) : 데이터베이스의 해당 데이터 삭제
assertEquals(2, this.h2dbNoticeRepository.count());
Optional<H2dbNotice> noticeData_delete = this.h2dbNoticeRepository.findById(1);
assertTrue(noticeData_delete.isPresent());
H2dbNotice noticeDelete = noticeData_delete.get();
this.h2dbNoticeRepository.delete(noticeDelete);
assertEquals(1, this.h2dbNoticeRepository.count());
}
자식테이블(Reply) Create
- 부모테이블(Notice)의 외래 키(ID)를 참조하여 데이터 호출
- Optional<h2dbNotice> ref_h = this.notice_repo.findById(n)
- assertTrue(ref_h.isPresent());
- H2dbNotice data = ref_h.get()
- 엔티티 객체를 생성하여 외래 키와 연결하여 리파지토리에 데이터 입력 (DB에 저장)
- H2dbReply rep = new H2dbReply();
- rep.setColumn("Message");
- rep.setH2dbNotice(data);
- 부모 테이블(Notice)의 키와 연결
- this.repo_reply.save(rep);
// H2DbTestApplication.java
// 공지사항에 댓글 작성하기
void testJpa_create_replyFromNotice() {
// 참조 할 데이터 호출
Optional<H2dbNotice> refData_notice = this.h2dbNoticeRepository.findById(1);
assertTrue(refData_notice.isPresent());
H2dbNotice refData = refData_notice.get();
// 데이터 생성
// 참조한 엔티티와 참조한 엔티티의 데이터를 세터로 연결
// 실제 데이터베이스에서는 외래 키가 되어있으므로 참조되는 테이블의 세터 설정이 가능
// 외래 키 설정은 엔티티에서 ManyToOne을 설정하였기 때문
H2dbReply replyCreate1 = new H2dbReply();
replyCreate1.setContent("첫번째 공지사항이네요!");
replyCreate1.setH2dbNotice(refData);
replyCreate1.setCreateDate(LocalDateTime.now());
this.h2dbReplyRepository.save(replyCreate1);
}
자식 테이블(Reply) Read
- 위에서 언급한 Read와 동일하므로 설명은 생략합니다.
- 자식 테이블 객체는 .get 메소드에 부모 테이블의 참조된 값을 불러 올 수 있음
- 예시) replyData.getH2dbNotice().getId()
// 공지사항의 댓글 조회하기
void testJpa_read_replyFromNotice() {
Optional<H2dbReply> replyData_read = this.h2dbReplyRepository.findById(1);
assertTrue(replyData_read.isPresent());
H2dbReply replyData = replyData_read.get();
assertEquals(1, replyData.getH2dbNotice().getId());
DB 테스트
- 테스트를 위해서는 테스트 할 메소드 위에 @Test 입력
- 해당 포스팅에서는 테스트 파일에 CRUD를 모두 작성하였습니다.
- 때문에 모든 메소드에 테스트 애너테이션을 사용하면 에러가 날 확률이 높습니다.
- 그래서 메소드를 하나씩 나누어서 실행하도록 합니다.
- 전체 소스 코드를 복사하여 하나의 테스트 애너테이션만 주석을 풀고 테스트
- 다른 테스트는 주석 처리
@Test
테스트 파일 전체 소스 코드
// H2DbTestApplication.java
package com.example.h2dbtest;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.example.h2dbtest.notice.H2dbNotice;
import com.example.h2dbtest.notice.H2dbNoticeRepository;
import com.example.h2dbtest.reply.H2dbReplyRepository;
@SpringBootTest
class H2DbTestApplicationTest {
// 리포지터리 객체 주입
@Autowired
private H2dbNoticeRepository h2dbNoticeRepository;
@Autowired
private H2dbReplyRepository h2dbReplyRepository;
@Test
// 데이터 생성하기
void testJpa_create() {
H2dbNotice noticeCreate1 = new H2dbNotice();
noticeCreate1.setSubject("첫번째 공지사항입니다.");
noticeCreate1.setContent("첫번째 공지사항이 작성되었습니다.");
noticeCreate1.setCreateDate(LocalDateTime.now());
this.h2dbNoticeRepository.save(noticeCreate1);
// 첫번째 공지사항 저장
H2dbNotice noticeCreate2 = new H2dbNotice();
noticeCreate2.setSubject("두번째 게시판 공지");
noticeCreate2.setContent("두번째 글 입니다.");
noticeCreate2.setCreateDate(LocalDateTime.now());
this.h2dbNoticeRepository.save(noticeCreate2);
// 두번째 공지사항 저장
}
// !!! 데이터 확인을 위해 JUnit의 assertEquals 사용 !!!
// !!! assertEquals(기댓값, 실제값) !!!
//@Test
// 데이터 조회하기 findAll
void testJpa_read_findAll() {
// 모든 데이터 조회 findAll
// 값이 동일한테 확인, 동일하지 않으면 테스트는 실패 처리
List<H2dbNotice> noticeData_findAll = this.h2dbNoticeRepository.findAll();
assertEquals(2, noticeData_findAll.size());
H2dbNotice noticeFindAll = noticeData_findAll.get(0);
assertEquals("첫번째 공지사항입니다.", noticeFindAll.getSubject());
}
//@Test
// 데이터 조회하기 findById
void testJpa_read_findById() {
// 조회 findById
// findById는 Optional 타입으로 값 반환
// isPresent : 데이터 객체의 Null 확인
Optional<H2dbNotice> noticeData_findById = this.h2dbNoticeRepository.findById(2);
if(noticeData_findById.isPresent()) {
H2dbNotice noticeFindById = noticeData_findById.get();
assertEquals("두번째 게시판 공지", noticeFindById.getSubject());
}
}
//@Test
// 데이터 조회하기 findBySubject
void testJpa_read_findBySubject() {
// 조회 findBy + Column
// findBy + 엔티티 속성명(컬럼) 메소드 : 해당 속성의 값으로 데이터 조회
// 리파지토리에 해당 메소드를 정의해야 사용 가능
H2dbNotice noticeData_findBySubject = this.h2dbNoticeRepository.findBySubject("첫번째 공지사항입니다.");
assertEquals(1, noticeData_findBySubject.getId());
}
//@Test
// 데이터 조회하기 findByLike
void testJpa_read_findByLike() {
// 특정 문자열 조회 findBy + Column + Like
// findBy + 엔티티 속성명(컬럼) Like : 특정 문자열로 해당 속성의 데이터 조회
// 리파지토리에 해당 메소드를 정의해야 사용 가능
// %검색어 : 검색어로 끝나는 문자열
// 검색어% : 검색어로 시작하는 문자열
// %검색어% : 검색어가 포함되는 문자열
List<H2dbNotice> noticeData_findLike = h2dbNoticeRepository.findBySubjectLike("%공지%");
H2dbNotice noticeFindLike = noticeData_findLike.get(0);
assertEquals("첫번째 공지사항입니다.", noticeFindLike.getSubject());
}
//@Test
// 데이터 수정하기
void testJpa_update() {
// 조회와 비슷하지만 save를 이용하여 해당 속성을 저장
// 데이터베이스에서는 update문을 실행
Optional<H2dbNotice> noticeData_update = this.h2dbNoticeRepository.findById(1);
assertTrue(noticeData_update.isPresent());
H2dbNotice noticeUpdate = noticeData_update.get();
noticeUpdate.setSubject("수정된 제목");
this.h2dbNoticeRepository.save(noticeUpdate);
}
//@Test
// 데이터 삭제하기
void testJpa_delete() {
// 리파지토리.delete(데이터) : 데이터베이스의 해당 데이터 삭제
assertEquals(2, this.h2dbNoticeRepository.count());
Optional<H2dbNotice> noticeData_delete = this.h2dbNoticeRepository.findById(1);
assertTrue(noticeData_delete.isPresent());
H2dbNotice noticeDelete = noticeData_delete.get();
this.h2dbNoticeRepository.delete(noticeDelete);
assertEquals(1, this.h2dbNoticeRepository.count());
}
//@Test
// 공지사항에 댓글 작성하기
void testJpa_create_replyFromNotice() {
// 참조 할 데이터 호출
Optional<H2dbNotice> refData_notice = this.h2dbNoticeRepository.findById(1);
assertTrue(refData_notice.isPresent());
H2dbNotice refData = refData_notice.get();
// 데이터 생성
// 참조한 엔티티와 참조한 엔티티의 데이터를 세터로 연결
// 실제 데이터베이스에서는 외래 키가 되어있으므로 참조되는 테이블의 세터 설정이 가능
// 외래 키 설정은 엔티티에서 ManyToOne을 설정하였기 때문
H2dbReply replyCreate1 = new H2dbReply();
replyCreate1.setContent("첫번째 공지사항이네요!");
replyCreate1.setH2dbNotice(refData);
replyCreate1.setCreateDate(LocalDateTime.now());
this.h2dbReplyRepository.save(replyCreate1);
}
//@Test
// 공지사항의 댓글 조회하기
void testJpa_read_replyFromNotice() {
Optional<H2dbReply> replyData_read = this.h2dbReplyRepository.findById(1);
assertTrue(replyData_read.isPresent());
H2dbReply replyData = replyData_read.get();
assertEquals(1, replyData.getH2dbNotice().getId());
}
//@Test
// 공지사항의 댓글 조회하기
void testJpa_read_replyFromNotice() {
Optional<H2dbReply> replyData_read = this.h2dbReplyRepository.findById(1);
assertTrue(replyData_read.isPresent());
H2dbReply replyData = replyData_read.get();
assertEquals(1, replyData.getH2dbNotice().getId());
}
}
프로젝트 디렉토리 구조
Error
Setter Error
- set이 먹히지 않을 경우 (setContent 등등)
- 롬복(lombok)이 제대로 작동하는지 확인
- 그래도 안된다면 수동으로 게터/세터(Getter/Setter)를 설정
// ID 컬럼 Getter/Setter 예시
// 엔티티 클래스에서 정의
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
JUnit Test Error
- 실패한 메소드를 누르면 Failure Trace로 확인 할 수 있음
- 해당 오류에서 에러 확인 가능
- 포스팅에서 발생한 오류는 누르면 결과 비교 창 출력
- 내가 입력한 값 ' 두번째 게시판 공지'
- 실제 값 ' 첫번째 공지사항입니다.'
- 에러 확인
- 데이터베이스에서 1번 ID 값을 가져왔는데, 내가 확인한 값은 2번 ID 값이였음
DB ID Index Error
- 잦은 테스트로 ID의 넘버링이 맞지 않을 경우
- 테스트로 작성, 삭제를 반복하다보면 ID가 1, 2가 아닐 때 인덱스를 초기화하여 1부터 시작
- H2 인덱스 초기화
- H2 로컬 DB 쿼리창에 아래의 코드를 입력하면 인덱스가 1부터 다시 시작
ALTER TABLE H2DB_NOTICE ALTER COLUMN ID RESTART WITH 1
참고
- 웹페이지 생성하고 DB의 데이터 목록 조회하기
- 포스팅의 내용이 길어 다음 포스팅으로 대체합니다.
https://luvris2.tistory.com/412
Spring Boot - 웹 페이지에 DB 데이터 조회하기 (컨트롤러, 모델, html)
포스팅 정보 - 해당 포스팅에서 사용한 툴, 라이브러리 IDE : STS4 Project : Gradle Project DB : H2 필요 라이브러리 추가 의존성 추가 (dependencies) build.gradle dependencies { implementation 'org.springframework.boot:spring-b
luvris2.tistory.com
- 위키독스 - 점프 투 스프링부트 - 리포지터리
반응형