본 문서는 인프런의 실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발 (김영한) 강의를 공부하며 작성한 개인 노트입니다.
순서: 회원 엔티티 코드 다시 보기 > 회원 리포지토리 개발 > 서비스 개발 > 기능 테스트
💐 회원 리포지토리 개발
@PersistenceContext
- 스프링이 entity manager를 만들어서 주입(injection)해줌
@PersistenceContext
private EntityManager em;
em.createQuery(JPAQuery, 반환타입)
- JPA 쿼리를 직접 입력하여 사용
- findAll() 등의 메서드에 사용됨
public List<Member> findAll() {
return em.createQuery("select m from Member m", Member.class)
.getResultList();
}
Named parameters
public List<Member> findByName(String name) {
return em.createQuery("select m from Member m where m.name = :name", Member.class)
.setParameter("name", name)
.getResultList();
}
- 쿼리 내 :variable는 .setParameter에서 정의됨
- .setParameter(쿼리 내 변수명, 변수값)
🌾 회원 서비스 개발
@Transactional
- JPA의 모든 데이터 변경/로직은 가급적이면 트랜잭션 안에서 실행되어야함
- 클래스 레벨에 에노테이션
- 옵션: readOnly
- @Transactional(readOnly = true)
- JPA가 조회만 하는 기능에서는 성능 최적화 가능
- 조회 기능이 많은 클래스는 클래스 전체에 이 옵션을 적용하고 데이터 변경이 필요한 메서드에만 @Transactional 에노테이션 추가
중복 회원 방지 기능
private void validateDuplicateMember(Member member) {
List<Member> findMembers = memberRepository.findByName(member.getName());
if (!findMembers.isEmpty()) {
throw new IllegalStateException("이미 존재하는 회원입니다");
}
}
- 같은 이름의 두 회원이 동시 가입할 때 이 메서드가 동시호출 되면서 로직이 실패할 수 있음
- 솔루션: 디비에 UNIQUE 제약 걸기
@Autowired
@Autowired
private MemberRepository memberRepository;
솔루션1: setter injection
private MemberRepository memberRepository;
@Autowired
public void setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
- 단점: 런타임에 누군가 바꿔버릴 수도 있음
- 동작 중에 바꿀 이유 없음
솔루션2: 생성자 Injection
private MemberRepository memberRepository;
@Autowired
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
- 장점: 런타임 중에 바뀌지 않음, 테케에도 활용 가능
- 생성자가 하나일 경우에는 스프링이 @Autowired 에노테이션 없이도 자동 injection
솔루션3: @AllArgsConstructor
- 클래스 레벨 에노테이션
- 생성자 injection 자동으로 만들어줌
솔루션4: @RequiredArgsConstructor
- 클래스 레벨 에노테이션
- final 설정된 필드만 생성자 injection에 포함하여 자동 생성
- 스프링 부트가 EntityManager도 (@PersistentContext 대체로) @Autowired로 injection 지원을 해주기 때문에 솔루션 3/4 적용 가능
🪷 회원 기능 테스트
@RunWith(SpringRunner.class)
@SpringBootTest
class MemberServiceTest {
...
}
@RunWith
@SpringBootTest
- 스프링 부트 띄우고 테스트
- @Autowired을 위해 꼭 필요
@Transactional
- 테스트에도 데이터 변경이 필요함
- 테스트마다 트랜잭션 시작
- @Transactional이 있어야 데이터 롤백 가능
- 자동으로 트랜잭션마다 롤백해서 디비에 반영이 안됨
강제 반영하는 솔루션
- @Rollback(false)
- em.flush();
@Autowired private EntityManager em;
public void 회원가입() {
...
em.flush();
}
fail("");
- org.junit.Assert.*
- 테스트케이스에서 이 라인까지 오면 안됨 > 오면 실패한 것
테스트케이스를 위한 설정
- 테스트는 케이스 격리된 환경에서 실행 & 끝난 후 데이터 초기화가 효과적
- 테스트 케이스를 위한 스프링 환경과 애플리케이션 실행 환경은 보통 다름
- 설정 파일을 다르게 이용하자
- test 디렉토리 안에 application.yml을 따로 만들면 테스트 실행 시 main의 설정 파일을 무시하고 test 디렉토리 내의 파일을 참조한다
- 본 파일의 spring.datasource.url을 메모리로 바꾸면됨
- 스프링 부트는 기본 셋팅이 메모리 모드기 때문에 datasource, jpa 등 모두 주석처리 가능