Back-End/JPA

JPA 활용 - 회원 도메인 개발 및 테스트코드 작성

LightSource 2023. 2. 21. 19:26

회원 도메인 개발하기

개발 순서 : 엔티티 설계 → 리포지토리, 서비스 개발 → 테스트 케이스 작성 및 검증 → 웹 계층 적용

앞에서는 엔티티 작성을 마쳤고, 이를 통해 애플리케이션 구현을 위한 리포지토리와, 서비스를 작성할 것이다.

이번 애플리케이션 구조는 계층형 구조를 사용한다.

  • 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 : 반복 가능한 테스트 지원, 테스트를 실행할 때마다 트랜색션 실행 및 테스트 완료시 트랜잭션을 강제로 롤백(테스트 케이스에서만 롤백된다)

참고

https://www.inflearn.com/course/스프링부트-JPA-활용-1/dashboard

반응형