구현 기능

  • 회원 등록
  • 회원 목록 조회

 

순서

  1. 회원 엔티티 코드 다시 보기
  2. 회원 리포지토리 개발
  3. 회원 서비스 개발
  4. 회원 기능 테스트

 

 

회원 리포지토리 개발


MemberRepository  클래스

package repository;

import jpabook.jpashop.domain.Member;
import org.springframework.stereotype.Repository;
import java.util.*;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Repository     // componentscan에 의하여 스프링 빈으로 관리됨
public class MemberRepository {

    // PersistenceContext는 스프링이 생성한 EntityManager를 자동으로 주입시켜줌
    @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() {
        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는 @Component 스캔의 대상이기 때문에 자동으로 스프링 빈에서 관리합니다.

  • @PersistenceContext는 스프링이 생선한 EntityManager를 자동으로 주입시켜 줍니다.
참고로 EntityManagerFactory를 쓰고 싶다면 @PersistenceUnit을 사용하시면 됩니다.

 

 

 

회원 서비스 개발


MemberService

package jpabook.jpashop.service;

import jpabook.jpashop.domain.Member;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import repository.MemberRepository;

import java.util.List;

@Service
@RequiredArgsConstructor            // final로 이루어진 필드로만 생성자를 만들어 줌
public class MemberService {

    private final MemberRepository memberRepository;        // 한번 초기화하면 바꿀 일이 없으니 final 선언

    /**
     * 회원 가입
     */
    @Transactional
    public Long join(Member member){
        validateDuplicateMember(member);
        memberRepository.save(member);
        return member.getId();
    }

    /**
     * 같은 이름이 있는 회원은 등록 안되도록
     */
    private void validateDuplicateMember(Member member) {
        //exception
        // 이러한 경우 멀티스레드 환경에서 문제가 될 수 있다.
        // 방지하기 위해서 member의 name을 unique 제약조건을 거는 것을 권장한다.
        List<Member> findMembers = memberRepository.findByName(member.getName());

        if(!findMembers.isEmpty()){
            throw new IllegalStateException("이미 존재하는 회원입니다.");
        }
    }

    /**
     * 회원 전체 조회
     */
    @Transactional(readOnly = true)     // 조회 성능 최적화
    public List<Member> findMembers() {
        return memberRepository.findAll();
    }

    /**
     * 멤버 1명 조회
     */
    @Transactional(readOnly = true)     // 조회 성능 최적화
    public Member findOne(Long memberId){
        return memberRepository.findOne(memberId);
    }
}

코드는 다르지만 같은 동작을 합니다. @RequiredArgsConstructor은 왼쪽의 코드 기능을 제공합니다.

 

Spring Data JPA를 사용하지 않는 경우 @PersistenceContext를 사용해야 bean에서 injection시켜주지만, Spring Data JPA를 사용하는 경우 @PersistenceContext를 사용하지 않고 @Autowired만 사용해도 injection 시켜줍니다. 

따라서, MemberRepository를 아래와 같이 변경할 수 있습니다.

 

 

 

 

회원 기능 테스트


MemberServiceTest

package jpabook.jpashop.service;

import jpabook.jpashop.domain.Member;
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.annotation.Rollback;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
import jpabook.jpashop.repository.MemberRepository;

import static org.junit.Assert.*;

@RunWith(SpringRunner.class)    // "junit을 실행할 때, 스프링과 엮어서 실행하겠다"를 의미합니다.
@SpringBootTest         // SpringBoot를 띄운 상태에서 Test를 할 때 추가해야되는 어노테이션 (추가하지 않으면 @Autowired기능에서 에러가 발생합니다.)
@Transactional  // 기본적으로 rollback
public class MemberServiceTest {

    @Autowired MemberService memberService;
    @Autowired MemberRepository memberRepository;

    @Test
    // @Rollback(false)  insert query 문을 보고싶다면 해보자
    // 또는 EntityManager를 만들어서 flush() 해주면된다.
    public void 회원가입() throws Exception {
        //given
        Member member = new Member();
        member.setName("kim");

        //when
        Long saveId = memberService.join(member);

        //then
        assertEquals(member, memberRepository.findOne(saveId));
    }
    
    @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("예외가 발생해야 한다.");
    }
}

 

 

given, when, then 이란?

"given을 주고 when 상황이 있을 때, then 이러한 결과가 나올 것이다."의 flow로 이어지는 테스트 작성 방법입니다.

 

예외 테스트하는 방법

예외가 터져야하는 테스트라면 @Test에 옵션을 추가하여 코드를 간결화 할 수 있습니다.

아래와 같이 try, catch 문을 쓰지 않아도 됩니다.

좌, 우 같은 코드

 

In-Memory로 테스트 하는 방법

 

H2 Database Engine

Using H2 Documentation Reference: SQL grammar, functions, data types, tools, API Features: fulltext search, encryption, read-only (zip/jar), CSV, auto-reconnect, triggers, user functions Embedded jdbc:h2:~/test 'test' in the user home directory jdbc:h2:/da

www.h2database.com

h2 Database 홈페이지에서 cheat sheet를 보시면,

In-Memory 설정하는 url이 있습니다. test를 할 땐 In-Memory로 하기위해 

프로젝트 test 폴더에 resources 파일을 만들어 main에 있는 application.yml을 복사해서 저장해줍니다. 그리고 application.yml 코드 중 datasource url을 jdbc:h2:mem:test로 수정해주시면 In-Memory로 테스트를 할 수 있습니다.

 

참고로 Spring boot에서 database에 대한 별다른 설정이 없다면 memory DB로 돌리기 때문에 Database 설정에 대한 모든 코드를 지워도 memory 모드로 사용가능 합니다.

 

 

 

 

 

출처

 

실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발 - 인프런 | 강의

실무에 가까운 예제로, 스프링 부트와 JPA를 활용해서 웹 애플리케이션을 설계하고 개발합니다. 이 과정을 통해 스프링 부트와 JPA를 실무에서 어떻게 활용해야 하는지 이해할 수 있습니다., - 강

www.inflearn.com

 

+ Recent posts