공부 내용을 정리하고 앞으로의 학습에 이해를 돕기 위해 작성합니다.
회원 리포지토리
package jpabook.jpashop.repository;
import jakarta.persistence.EntityManager;
import jpabook.jpashop.domain.Member;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
@RequiredArgsConstructor
public class MemberRepository {
private final EntityManager em;
public void save(Member member) {
em.persist(member);
}
public Member findOne(Long id) {
return em.find(Member.class, id);
}
public List<Member> findAll() {
return em.createQuery("select m from Member m", Member.class)
.getResultList();
}
public List<Member> findByName(String name) {
return em.createQuery("select m from Member m where m.name = :name", Member.class)
.setParameter("name", name)
.getResultList();
}
}
기술 설명
- @Repository: 스프링 빈으로 등록하고 JPA 예외를 스프링 기반 예외로 변환한다.
- @RequiredArgsConstructor: final 필드를 대상으로 생성자를 자동으로 생성해주는 롬복 어노테이션이다.
- EntityManager: JPA의 핵심 인터페이스로, 엔티티를 관리하고 데이터베이스와 상호작용한다.
기능 설명
- save(): 주어진 회원 객체를 영속성 컨텍스트에 저장하고 데이터베이스에 반영한다.
- findOne(): 주어진 ID로 회원을 검색해 반환한다.
- findAll(): 모든 회원을 리스트로 반환한다.
- findByName(): 주어진 이름으로 회원을 검색해 리스트로 반환한다.
회원 서비스
package jpabook.jpashop.service;
import jpabook.jpashop.domain.Member;
import jpabook.jpashop.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
/**
* 회원 가입
*/
@Transactional
public Long join(Member member) {
validateDuplicateMember(member); //중복 회원 검증
memberRepository.save(member);
return member.getId();
}
private void validateDuplicateMember(Member member) {
List<Member> findMembers = memberRepository.findByName(member.getName());
if (!findMembers.isEmpty()) {
throw new IllegalStateException("이미 존재하는 회원입니다.");
}
}
// 회원 전체 조회
public List<Member> findMembers() {
return memberRepository.findAll();
}
public Member findOne(Long memberId) {
return memberRepository.findOne(memberId);
}
}
기술 설명
- @Service: 서비스 계층을 나타내며 스프링 빈으로 등록된다.
- @Transactional: 트랜잭션 처리를 위한 어노테이션으로, 영속성 컨텍스트와 트랜잭션을 관리한다. readOnly=true는 읽기 전용 트랜잭션을 설정하여 성능을 최적화한다.
- @Autowired: 스프링에서 의존성 주입을 처리해주는 어노테이션으로, 생성자가 하나인 경우 생략 가능하다.
- final: 변경 불가능한 객체를 생성해 코드의 안정성을 높인다.
기능 설명
- join(): 회원 가입을 처리하며 중복 회원을 검증한 후 저장한다.
- findMembers(): 모든 회원을 조회한다.
- findOne(): 주어진 회원 ID로 특정 회원을 조회한다.
필드 주입 vs 생성자 주입
1. 필드 주입
public class MemberService {
@Autowired
MemberRepository memberRepository;
...
}
- 필드에 직접 @Autowired를 사용하여 의존성을 주입한다.
- 테스트나 유지보수 시에 유연성이 떨어지며, 변경 가능성이 있는 코드가 될 수 있다.
2. 생성자 주입
public class MemberService {
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
...
}
- 생성자 주입은 의존성을 주입하는 권장 방식으로, final 키워드를 통해 주입받은 의존성을 변경할 수 없도록 하여 안정성을 높인다.
- 생성자가 하나뿐인 경우, @Autowired를 생략할 수 있으며, 롬복의 @RequiredArgsConstructor를 사용하면 자동으로 생성자를 생성해준다.
- 이렇게 하면 코드가 더 간결해지고, 컴파일 시점에 의존성 주입 오류를 잡을 수 있어 안전한 객체를 생성할 수 있다.
3. Lombok 활용
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
...
}
- @RequiredArgsConstructor를 사용하면 final이 붙은 필드에 대해 자동으로 생성자를 만들어주므로, 코드 작성량이 줄어들고 가독성이 향상된다.
참고
@Repository
@RequiredArgsConstructor
public class MemberRepository {
private final EntityManager em;
...
}
- 스프링 데이터 JPA에서는 EntityManager도 주입받을 수 있다. @Repository와 @RequiredArgsConstructor를 사용하면 EntityManager도 간편하게 주입된다.
회원 기능 테스트
설정 파일
테스트는 격리된 환경에서 실행되므로 메모리 DB(H2)를 사용하는 것이 가장 이상적이다.
이를 위해 test/resources/application.yaml 파일에 테스트 환경 설정을 추가한다.
spring:
# datasource:
# url: jdbc:h2:mem:test
# username: sa
# password:
# driver-class-name: org.h2.Driver
# jpa:
# hibernate:
# ddl-auto: create
# properties:
# hibernate:
# # show_sql: true
# format_sql: true
logging.level:
org.hibernate.SQL: debug
org.hibernate.orm.jdbc.bind: trace
- 이 설정은 스프링 부트에서 기본적으로 메모리 DB를 사용하게 하며, 테스트가 끝나면 데이터를 초기화한다.
- Hibernate의 ddl-auto: create 설정을 통해 매번 스키마를 생성하고 테스트가 끝나면 삭제한다.
테스트 요구사항
- 회원가입 성공: 회원을 정상적으로 가입시키고, 데이터베이스에 저장된 회원을 확인한다.
- 중복 회원 예외 처리: 동일한 이름의 회원이 중복으로 가입하려고 할 때 예외가 발생해야 한다.
테스트 코드
package jpabook.jpashop.service;
import jpabook.jpashop.domain.Member;
import jpabook.jpashop.repository.MemberRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class MemberServiceTest {
@Autowired
MemberService memberService;
@Autowired
MemberRepository memberRepository;
@Test
public void 회원가입() throws Exception {
// given
Member member = new Member();
member.setName("kim");
// when
Long savedId = memberService.join(member);
// then
assertEquals(member, memberRepository.findOne(savedId));
}
@Test(expected = IllegalStateException.class)
public void 중복_회원_예외() throws Exception {
// given
Member member1 = new Member();
member1.setName("kim");
Member member2 = new Member();
member2.setName("kim");
// when
memberService.join(member1);
memberService.join(member2);
// then
fail("예외가 발생해야 한다.");
}
}
- 테스트 결과, @Transactional 덕분에 테스트가 끝난 후 트랜잭션이 롤백되어 DB에 insert 쿼리가 반영되지 않았다. 따라서 테스트 중 데이터 변경이 실제로 DB에 저장되지 않고, 안전하게 초기화된다.
기술 설명
- @RunWith(SpringRunner.class): 스프링과 JUnit을 통합해 주는 어노테이션으로, 스프링 컨텍스트에서 테스트를 실행할 수 있게 한다.
- @SpringBootTest: 스프링 부트를 실제로 실행하여 테스트 환경을 제공한다.
- @Transactional: 테스트가 끝나면 트랜잭션을 자동으로 롤백하여 데이터베이스에 영향을 주지 않는다.
기능 설명
- 회원가입 테스트: 회원을 가입시키고 정상적으로 데이터베이스에 저장된 회원 객체가 일치하는지 검증한다.
- 중복 회원 예외 처리 테스트: 동일한 이름의 회원이 가입되면 예외가 발생하는지 검증한다.
참고
- 트랜잭션이 적용되어 테스트 중 발생한 데이터 변경은 모두 롤백된다.
- 테스트 작성 시 Given, When, Then 구조를 사용하면 테스트의 의도가 명확해지고 가독성이 향상된다.
'BackEnd > JPA' 카테고리의 다른 글
[JPA] 주문 도메인 개발 (3) | 2024.10.03 |
---|---|
[JPA] 상품 도메인 개발 (0) | 2024.10.03 |
[JPA] 도메인 분석 설계(3) (2) | 2024.10.01 |
[JPA] 도메인 분석 설계(2) (0) | 2024.09.26 |
[JPA] 도메인 분석 설계(1) (2) | 2024.09.23 |