애플리케이션 구현 준비
구현 요구사항
회원 기능
- 회원 등록
- 회원 조회
상품 기능
- 상품 등록
- 상품 수정
- 상품 조회
주문 기능
- 상품 주문
- 주문 내역 조회
- 주문 취소
예제를 단순화 하기 위해 다음 기능은 구현X
로그인과 권한 관리X
파라미터 검증과 예외 처리X
상품은 도서만 사용
카테고리는 사용X
배송 정보는 사용X
애플리케이션 아키텍쳐

계층형 구조 사용
- controller, web: 웹 계층
- service: 비즈니스 로직, 트랜잭션 처리
- repository: JPA를 직접 사용하는 계층, 엔티티 매니저 사용
- domain: 엔티티가 모여 있는 계층, 모든 계층에서 사용
컨트롤러가 바로 리포지토리에 접근 가능하도록 유연한 구조로 설계 예정
패키지 구조
jpabook.jpashop
- domain
- exception
- repository
- service
- web
- (api)
개발 순서: 서비스, 리포지토리 계층을 개발하고, 테스트 케이스를 작성해서 검증, 마지막에 웹 계층 적용
회원 도메인 개발
- 회원 엔티티 코드 다시 보기
- 회원 리포지토리 개발
- 회원 서비스 개발
- 회원 기능 테스트
회원 리포지토리 개발
PK가 아닌 컬럼으로 조회하거나, 모든 객체를 조회하는 메서드는 JPQL 쿼리문을 작성해야 한다.
JPQL은 테이블을 대상으로 쿼리하는 SQL과 다르게 엔티티를 대상으로 쿼리한다.
EntityManager 작동 방식
- em.persist(entity): 영속성 컨텍스트에 엔티티를 주입하고 트랜잭션이 커밋되는 시점에 데이터베이스에 insert 쿼리를 한다. 만일 entity의 id로 설정한 필드에 @GeneratedValue 옵션이 붙어있으면 영속성 컨텍스트에 엔티티가 들어오는 시점에 id 값을 생성하여 필드에 넣어준다.
- em.find(반환 타입, id): 단건 조회
- em.createQuery(jpql 쿼리문, 반환 타입)
영속성 컨텍스트
엔티티를 영구 저장하는 환경
애플리케이션과 데이터베이스 사이에서 객체를 보관하는 가상의 데이터베이스 같은 역할을 한다.
영속성 컨텍스트는 엔티티를 식별자 값으로 구분한다. 따라서 영속 상태는 식별자 값이 반드시 있어야 한다.
데이터를 조회하고 변경하는 로직이 포함되면 트랜젝션 안에서 실행되도록 하는 것이 좋다.
@Transactional은 스프링에 이미 의존적인 애플리케이션이라면 spring 패키지의 것을 사용한다. 사용할 수 있는 옵션이 많다.
데이터 조회 메서드라면 readOnly=true 옵션을 사용하여 리소스를 절약할 수 있다. (default: false)
중복 데이터의 존재 여부를 검증하는 로직에서 멀티 쓰레드 환경을 고려하여 단순히 코드 내에서 데이터를 조회하는 것 뿐만 아니라, 데이터베이스에서 해당 컬럼을 유니크 제약 조건으로 설정하는 것이 보다 안전하다.
EntityManager injection 리팩토링
▽기존 코드
@Repository
public class MemberRepository{
@PersistanceContext
private EntityManager em;
}
스프링 부트 JPA를 사용하면 @PersistanceContext 대신 @Autowired 를 통해 인젝션이 가능하다.
▽생성자 주입+롬복을 활용한 코드
@Repository
@RequiredArgsConstructor
public class MemberRepository{
private final EntityManager em;
}
- 생성자 주입 방식을 권장
- 변경 불가능한 안전한 객체 생성 가능
- 생성자가 하나면, @Autowired 를 생략할 수 있다.
- final 키워드를 추가하면 컴파일 시점에 memberRepository 를 설정하지 않는 오류를 체크할 수 있다. (보통 기본 생성자를 추가할 때 발견)
회원 기능 테스트
테스트 요구사항
- 회원가입을 성공해야 한다.
- 회원가입 할 때 같은 이름이 있으면 예외가 발생해야 한다
@ExtendWith(SpringExtension.class) : 스프링과 테스트 통합 (JUNIT4: @RunWith(SpringRunner.class))
@SpringBootTest : 스프링 부트 띄우고 테스트 (이게 없으면 @Autowired 다 실패)
@Transactional: 반복 가능한 테스트 지원, 각각의 테스트를 실행할 때마다 트랜잭션을 시작하고 테스트가 끝나면 트랜잭션을 강제로 롤백 (테스트 케이스 한정 롤백)
영속 엔티티의 동일성 보장
한 트랜젝션 내에서 식별자(PK) 값이 같은 엔티티를 조회하면 같은 객체가 반환된다.
영속성 컨텍스트에서 해당 식별자를 가진 엔티티는 딱 1개만 관리되기 때문이다.
테스트 케이스의 @Transactional
반복적인 테스트를 위해 각 테스트가 끝나면 트랜젝션을 커밋하지 않고 강제로 롤백해서 insert 쿼리가 날아가지 않는다. (정확히는 영속성 컨텍스트의 flush 진행 X)
테스트 케이스에서도 변경 사항을 데이터베이스 내에서 확인하고 싶다면 @Rollback 옵션을 false로 두거나 엔티티 매니저를 받아 직접 flush해주면 쿼리가 진행된다.
fail(): 해당 메서드가 실행되면 테스트가 실패한다. 즉, 이전에 메서드 밖으로 나가야 함(return/exception)
참고: 테스트 케이스 작성 고수 되는 마법: Given, When, Then (http://martinfowler.com/bliki/GivenWhenThen.html)
이 방법이 필수는 아니지만 이 방법을 기본으로 해서 다양하게 응용하는 것을 권장한다.
테스트 케이스를 위한 설정
테스트 케이스는 격리된 환경에서 실행하고, 끝나면 데이터를 초기화하는 것이 좋다. 그런 면에서 메모리 DB를 사용하는 것이 가장 이상적이다.
추가로 테스트 케이스를 위한 스프링 환경과, 일반적으로 애플리케이션을 실행하는 환경은 보통 다르므로 설정 파일(.yml)을 다르게 사용하자. 테스트 디렉터리에 설정 파일이 없으면 메인 디렉터리의 설정 파일대로 세팅한다.
테스트용 설정 파일 ( test/resources/application.yml )
H2 Database Engine Cheat Sheet - In Memory
url: jdbc:h2:mem:test
or DB에 대한 기본 설정이 없으면 스프링 부트는 자동으로 메모리 모드로 테스트를 실행한다.
*Inline Variable: ctrl+alt+N
'Advance I > Springboot & JPA' 카테고리의 다른 글
| 22.05.17 (0) | 2022.05.17 |
|---|---|
| 22.05.14 (0) | 2022.05.14 |
| 22.05.13 (0) | 2022.05.13 |
| 22.05.10 (0) | 2022.05.10 |
| 22.05.09 :: 실전! 스프링 부트와 JPA 활용 (0) | 2022.05.09 |