-
JPA로 CRUD 해보기 + 테스트코드프로젝트/게시판 프로젝트 2022. 4. 28. 22:15반응형
https://junuuu.tistory.com/270?category=997278
Spring Initializr로 프로젝트 생성하기 + MySQL연동까지
https://start.spring.io/ Proejct : Gradle Project Language : Java Packaging : Jar(REST API server로 만들기 때문에 JSP필요 x) - Spring 문서에서도 JSP를 피하라고 명시 Java : 11(16으로 변경해서 사용 예..
junuuu.tistory.com
이전에 이어서 Spring Initailzr로 스프링 부트 프로젝트를 생성했으며 MySQL까지 연동된 상황입니다.
지난번에 resources/application.properties 에서 JPA에 대한 설정을 할때 다음과 같은 설정을 했습니다.
spring.jpa.hibernate.ddl-auto=update
- create : 기존 테이블을 삭제하고 새로 생성 [ DROP + CREATE ]
- create-drop : CREATE 속성에 추가로 애플리케이션을 종료할 때 생성한 DDL을 제거 [ DROP + CREATE + DROP ]
- update : DB 테이블과 엔티티 매핑 정보를 비교해서 변경 사항만 수정 [ 테이블이 없을 경우 CREATE ]
- validate : DB 테이블과 엔티티 매핑정보를 비교해서 차이가 있으면 경고를 남기고 애플리케이션을 실행하지 않음
- none : 자동 생성 기능을 사용하지 않음
여기서 update를 선택했기 때문에 DB 테이블과 엔티티 매핑 정보를 비교해서 변경 사항만 수정하고 테이블이 없을 경우에는 CREATE 해준다는 것을 알 수 있습니다.
이제 Entity를 생성해 보겠습니다.
원래 계획된 회원정보에는 여러가지가 있지만 현재는 테스트 목적으로 PK인 member_Id, user_Id만 생성해 보겠습니다.
package anthill.Anthill.domain; import lombok.*; import javax.persistence.*; import static lombok.AccessLevel.PROTECTED; @NoArgsConstructor(access = PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) @Builder @Getter @Table(name = "member") @Entity public class Member { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) //MySQL의 AUTO_INCREMENT를 사용 @Column(name="member_Id") private Long id; @Column(name ="user_Id", nullable = false, unique = true, length = 20) String userId; }
@Entity
DB 테이블을 뜻합니다.
JPA Repository를 사용하기 위해서는 반드시 @Entity 어노테이션이 명시되어 있어야 합니다.
@Table
DB 테이블명을 명시했습니다.
테이블 명과 클래스 명이 동일한 경우 생략 가능하지만 명시적으로 보일 수 있도록 기입해 주었습니다.
@Getter
Lombok의 어노테이션인 @Getter입니다.
해당 어노테이션은 Getter 메서드인 getXXX 메서드들을 생성해줍니다.
ex) member.getUserId(), member.getId()
@Builder
Lombok의 어노테이션인 @Builder입니다.
Builder 어노테이션을 사용하기 위해서는 @AllArgsConstructor와 @NoArgsConstructor가 필요합니다.
생성 패턴인 빌더 패턴을 편리하게 사용하기 위한 어노테이션이며 해당 어노테이션에 대해서는 좀 더 뒤에 자세하게 다루어 보겠습니다.
@Id
DB 테이블의 Primary Key를 뜻합니다.
@GeneratedValue
Primary Key의 키 생성 전략(Strategy)을 설정하고자 할 때 사용
- GenerationType.IDENTITY : MySQL의 AUTO_INCREMENT 방식을 이용
- GenerationType.AUTO(default) : JPA 구현체(Hibernate)가 생성 방식을 결정
- GenerationType.SEQUENCE : DB의 SEQUENCE를 이용해서 키를 생성. @SequenceGenerator와 같이 사용
- GenerationType.TABLE : 키 생성 전용 테이블을 생성해서 키 생성. @TableGenerator와 함께 사용
여기에서 memberID는 AUTO_INCREMENT 방식을 사용하기 때문에 GenerationType.IDENTITY 방식을 사용했습니다.
@Column
DB Column을 이름을 명시합니다.
여기서는 member_Id로 명시했습니다.
@Column과 반대로 테이블에 칼럼으로 생성되지 않는 필드의 경우엔 @Transient 어노테이션을 적용한다.
또한 userId의 경우에는 null값이면 안되기 때문에 nullable = false, 항상 유일한 값을 가져야 하기 때문에 unique = true, varchar의 길이는 20으로 설정하였습니다.
이렇게 Member 클래스를 작성하게 되고 스프링을 실행한 후 DB를 확인해보면 Member 테이블이 생성되어 있습니다!
이제 JPA Repository를 만들어 보겠습니다.
package anthill.Anthill.repository; import anthill.Anthill.domain.Member; import org.springframework.data.jpa.repository.JpaRepository; public interface MemberRepository extends JpaRepository<Member, Long> { }
끝입니다.
MemberRepository에 JPARepository <Entity, PK의 타입>을 상속해주면 끝입니다.
MemberRepository를 이용해서 작성된 테이블에 SQL문 없이 CRUD 작업을 할 수 있게 됩니다.
Spring Data JPA는 자동으로 스프링의 빈(bean)으로 등록됩니다.
만약 나는 userId로도 조회하고 싶은데? 라면 아래와 같이 추가만 해주면 끝입니다.
public interface MemberRepository extends JpaRepository<Member, Long> { Member findByUserId(String userId); }
<반환타입> findBy{변수명}(<타입> 인자명)을 선언만 하면 알아서 JPA가 구현해 줍니다.
Entity와 JPA Repository를 만들었습니다. 이제 테스트를 해보겠습니다.
package anthill.Anthill.repository; import anthill.Anthill.domain.Member; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.dao.DataIntegrityViolationException; import javax.transaction.Transactional; import java.util.List; import java.util.Optional; import java.util.stream.IntStream; import static org.junit.jupiter.api.Assertions.*; @Transactional @SpringBootTest class MemberRepositoryTest { @Autowired MemberRepository memberRepository; @DisplayName("CREATE 테스트") @Test public void insertSuccessTest() { IntStream.rangeClosed(1, 10).forEach(i -> { Member member = Member.builder().userId("Test" + i).build(); memberRepository.save(member); }); List<Member> members = memberRepository.findAll(); Assertions.assertThat(members.size()).isEqualTo(10); } @DisplayName("DELETE 테스트") @Test public void deleteSuccessTest() { //given Member member = Member.builder().userId("Test").build(); memberRepository.save(member); //when memberRepository.deleteById(member.getId()); //then List<Member> members = memberRepository.findAll(); Assertions.assertThat(members.size()).isEqualTo(0); } @DisplayName("READ 테스트") @Test public void selectSuccessTest() { //given Member member = Member.builder().userId("test").build(); Member fail = Member.builder().userId("fail").build(); memberRepository.save(member); //when Optional<Member> result = memberRepository.findById(member.getId()); //then Assertions.assertThat(member.getUserId()).isEqualTo(result.orElse(fail).getUserId()); } @DisplayName("UPDATE 테스트") @Test public void updateSuccessTest() { //given Member member = Member.builder().userId("test").build(); Member fail = Member.builder().userId("fail").build(); memberRepository.save(member); //when Member updateMember = Member.builder().id(member.getId()).userId("updated").build(); memberRepository.save(updateMember); //then Optional<Member> result = memberRepository.findById(member.getId()); Assertions.assertThat(result.orElse(fail).getUserId()).isEqualTo("updated"); } @DisplayName("데이터 무결성 테스트") @Test public void insertFailTest() { //given Member member = Member.builder().build(); //then assertThrows(DataIntegrityViolationException.class, () -> { //when memberRepository.save(member); }); } }
@SpringBootTest
해당 어노테이션은 애플리케이션 테스트에 필요한 거의 모든 의존성들을 제공합니다.
Spring Main Application인 @SpringBootApplication을 찾아가 하위의 모든 Bean을 Scan 합니다.
@Transcational
우리는 테스트할 때 재사용할 수 있는 테스트를 작성해야 합니다.
하지만 DB에 Insert 하게 되면 다시 테스트하기 위해서는 일일이 DB값을 지워줘야 합니다.
https://docs.spring.io/spring-framework/docs/current/reference/html/testing.html#testcontext-tx-rollback-and-commit-behavior 하지만 테스트에 @Transcational 어노테이션을 사용한다면 테스트가 완료되고 나면 자동으로 rolled back 되어 테스트 코드에 사용한 데이터를 관리하기 쉽습니다.
만약에 나는 DB에 실제로 데이터가 들어가는 걸 보고 싶다면?
@Transcational 어노테이션을 사용하지 않으면 됩니다.
@Autowired
MemberRepository를 사용하기 위해 객체를 주입받습니다.
테스트 메서드를 간단하게 설명해보겠습니다.
insertTest
10개의 Member 데이터를 저장하고, findAll()으로 불러와 해당 데이터가 10개인지를 검증
deleteTest
1개의 데이터를 저장하고, 그 데이터를 삭제합니다. 이후에 findAll()으로 불러와 데이터가 0개인지를 검증
selectTest
test라는 userId를 가지는 Member 데이터를 저장합니다.
이후에 해당 member의 ID를 통해 Member를 조회하고 그 결과가 test라는 userID를 가지는 Member인지 검증합니다.
updateTest
test라는 userID를 가지는 Member 데이터를 저장합니다.
이후에 같은 memberID를 가지며 updated라는 userID를 가지는 Member 데이터를 저장합니다.
JPA는 내부적으로 해당 엔티티의 @Id값이 일치하는지를 확인해서 insert 혹은 update 작업을 처리합니다.
여기에서는 update작업이 발생하게 됩니다.이후에 처음에 test라는 userID를 가지는 유저를 조회했을 때 userID값이 updated인지 검증합니다.
dataIntegrityTest
userID는 Not Null입니다.
즉, DB에서도 꼭 넣어줘야 하는 값인데 이 값을 가지지 않고 들어가면 어떻게 될까 궁금하여 빈값으로 save()를 호출해보니 DataIntegrityViolationException이 발생했습니다.
Exception 발생 따라서 assertThrows를 사용하여 해당 Exception이 터지는지 검증합니다.
결과
테스트 코드 통과 출처
https://dev-coco.tistory.com/85
[Spring Boot] MySQL & JPA 연동 및 테스트 (Gradle 프로젝트)
SpringBoot에서 MySQL 그리고 Spring Data JPA를 연동하는 방법에 대해 알아보도록 하겠습니다. 1. 프로젝트에 의존성 추가하기 build.gradle에 의존성을 아래와 같이 추가해줍니다. dependencies { implementatio..
dev-coco.tistory.com
Testing
The classes in this example show the use of named hierarchy levels in order to merge the configuration for specific levels in a context hierarchy. BaseTests defines two levels in the hierarchy, parent and child. ExtendedTests extends BaseTests and instruct
docs.spring.io
'프로젝트 > 게시판 프로젝트' 카테고리의 다른 글
Entity와 DTO를 분리하자! (0) 2022.05.04 @Builder 제대로 알고 사용해보기 (0) 2022.04.30 Spring Boot + MySQL 연동(feat. Spring initializr) (0) 2022.04.26 테이블의 PK는 어떤값으로 잡아야 할까? (0) 2022.04.21 프로젝트 데이터베이스 설계(RDBMS vs NoSQL 선정기준 + 특정 RDBMS 선정이유) (0) 2022.03.29