회원 도메인 개발하기
개발 순서 : 엔티티 설계 → 리포지토리, 서비스 개발 → 테스트 케이스 작성 및 검증 → 웹 계층 적용
앞에서는 엔티티 작성을 마쳤고, 이를 통해 애플리케이션 구현을 위한 리포지토리와, 서비스를 작성할 것이다.
이번 애플리케이션 구조는 계층형 구조를 사용한다.
- controller, web : 웹 계층
- service : 비즈니스 로직, 트랜잭션 처리
- repository : JPA를 직접 사용하는 계층, 엔티티 매니저 사용
- domain : 엔티티가 모여 있는 계층, 모든 계층에서 사용
회원 리포지토리 생성
@Repository
public class MemberRepository {
@PersistenceContext
private 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() { // JPQL작성
return em.createQuery("select m from Member m", Member.class).getResultList();
}
public List<Member> findByName(String name) { // JPQL작성 맟 name파라미터 바인딩
return em.createQuery("select m from Member m where m.name = :name", Member.class).setParameter("name", name)
.getResultList();
}
}
사용 기술
@Repository
: 스프링 빈으로 등록, JPA 예외를 스프링 기반 예외로 변환
@PersistenceContext
: 엔티티 매니저 주입
@PersistenceUnit
: 엔티티 매니저 팩토리 주입
사용 기능
save()
: 객체 저장, persist 메서드로 영속성 컨텍스트에 멤버 엔티티를 넣고, 트랜잭션 커밋 시점에 DB에 반영이 된다.find()
: 단건 조회, 파라미터로 type과 PK(Primary Key)를 받는다.findAll()
: createQuery를 통한 JPQL사용, JPQL은 테이블이 아닌 엔티티 객체를 대상으로 쿼리를 한다.findByName()
: createQuery를 통한 JPQL사용
회원 서비스 생성
@Service
@Transactional
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
//회원 가입
public Long join(Member member) {
validateDuplicateMember(member); //중복회원 검증 메서드
memberRepository.save(member);
return member.getId();
}
private void validateDuplicateMember(Member member) {
//EXCEPTION
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
속성: 데이터의 변경이 없는 읽기 전용 메서드에 사용하며, 영속성 컨텍스트를 플러시 하지 않으므로 약간의 성능 향상- 데이터베이스 드라이버가 지원하면 DB에서 성능향상(H2에선 지원x)
@Autowired
: 생성자 Injection을 많이 사용, 생성자가 하나면 생략가능
@RequiredArgConstructor
: 롬복에서 제공하는 어노테이션으로, @NonNull
이 붙은 필드와final이 선언된 필드들이 들어간 생성자를 생성해준다.
💡 스프링 필드 주입 대신에 생성자 주입을 사용하자!
- 변경 불가능한 안전한 객체를 생성 가능함
- 생성자가 하나면, @Autowired를 생략 가능함
- final 키워드 추가시, 컴파일 시점에 memberRepository를 설정하지 않는 오류를 체크가능
필드 주입
public class MemberService {
@Autowired
MemberRepository memberRepository;
...
}
생성자 주입
public class MemberService {
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
...
}
@RequiredArgConstructor
를 사용한 생성자 주입
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
}
→ 코드 깔끔!
회원 기능 테스트
테스트 요구사항
- 회원가입을 성공해야 한다
- 회원가입 할 떄 같은 이름이 있으면 예외가 발생해야 한다.
회원가입 테스트 코드
package jpabook.jpashop.service;
import jpabook.jpashop.domain.Member;
import jpabook.jpashop.repository.MemberRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import javax.persistence.EntityManager;
import javax.transaction.Transactional;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@SpringBootTest //Junit4의 @RunWith(SpringRunner.class)가 Junit5부터는 포함되어 있다.
@Transactional
class MemberServiceTest {
@Autowired MemberService memberService;
@Autowired
MemberRepository memberRepository;
@Autowired
EntityManager entityManager;
@Test
void 회원가입() throws Exception {
//given
Member member = new Member();
member.setName("kim");
//when
Long savedId = memberService.join(member);
//then
entityManager.flush();
assertEquals(member, memberRepository.findOne(savedId));
}
@Test
void 중복_회원_예외() throws Exception {
//given
Member member1 = new Member();
member1.setName("kim");
Member member2 = new Member();
member2.setName("kim");
//when
memberService.join(member1);
//then
IllegalStateException thrown = assertThrows(
IllegalStateException.class,
() -> memberService.join(member2));
assertEquals("이미 존재하는 회원입니다.", thrown.getMessage());
}
}
사용 기술
@SpringBootTest
: 스프링부트 띄우고 테스트(없으면@Autowired
동작x)@Transactional
: 반복 가능한 테스트 지원, 테스트를 실행할 때마다 트랜색션 실행 및 테스트 완료시 트랜잭션을 강제로 롤백(테스트 케이스에서만 롤백된다)
참고
반응형