제육's 휘발성 코딩
반응형

회원 도메인 개발

구현 기능

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

순서

  • 회원 레포지토리 개발
  • 회원 서비스 개발
  • 회원 기능 테스트

회원 레포지토리 개발

package jpabook.jpashop.repository;

import jpabook.jpashop.domain.Member;
import org.springframework.stereotype.Repository;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;

@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() {
        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();
    }
}
  • @PersistenceContext : 엔티티 매니저 주입
  • @PersistenceUnit : 엔티티 매니저 팩토리 주입 ( 매니저만 주입받으면 사용가능하므로 해당 애노테이션을 사용하는 경우는 거의 없다.)
  • @Repository : 스프링 빈으로 등록, JPA 예외를 스프링 기반 예외로 예외 변환
  • createQuery를 보면 Jpql을 사용했다. 파라미터가 들어가는 경우 : 를 사용하고 setParameter로 값을 넣어준다.

 

회원 서비스 개발

package jpabook.jpashop.service;

import jpabook.jpashop.domain.Member;
import jpabook.jpashop.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
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);
    }

}
  • @Transactional : 트랜잭션으로 관리하도록 지정 , readOnly=true 는 변경이 없는 읽기 전용 메서드임을 명시하여, 영속성 컨텍스트에 플러시하지 않게 되어 약간의 성능향상이 발생한다.
  • 생성자 주입은 @RequiredArgsContructor 를 사용하면 간단하게 표현할 수 있다.

 

회원 기능 테스트

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 org.springframework.transaction.annotation.Transactional;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
@Transactional
class MemberServiceTest {

    private final MemberService memberService;
    private final MemberRepository memberRepository;

    @Autowired
    public MemberServiceTest(MemberService memberService, MemberRepository memberRepository) {
        this.memberService = memberService;
        this.memberRepository = memberRepository;
    }

    @Test
    public void 회원가입() {
        // given
        Member member = new Member();
        member.setName("sasca");

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

        // then
        assertEquals(member, memberRepository.findOne(savedId));
    }

    @Test
    public void 중복_회원_예외() {
        // given
        Member member1 = new Member();
        member1.setName("sasca");

        Member member2 = new Member();
        member2.setName("sasca");

        // when
        memberService.join(member1);

        // then
        assertThrows(IllegalStateException.class, () ->  {
            memberService.join(member2);
        });
    }
}
  • Junit5 기반으로 테스트 코드를 작성했다.
  • 테스트 시점에서 @Transactional 은 실행할 때마다 트랙잭션으로 만들어서 커밋하고 끝나면 강제로 롤백한다.

 

메모리 DB 사용법

# test/resources/application.yml 생성 
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.type: trace
  • 스프링부트는 테스트환경에서 test/resources/application.yml 이 존재하면 테스트단에서 해당 yml을 찾아간다. 이 때, datasource 설정이 없으면 기본적으로 메모리 DB를 사용하고 create-drop 모드로 동작한다. 즉, 다음과 같이 주석처리가 되어있어도 자동으로 설정해준다.
  • 테스트 케이스는 격리된 환경에서 실행하고, 끝나면 데이터를 초기화하는 것이 좋다. 그런면에서 메모리 DB를 사용하는 것이 이상적이다.

본 포스팅은 '인프런 - 김영한님 강의(실전! 스프링 부트와 JPA 활용1)'를 토대로 정리한 내용입니다.

반응형
profile

제육's 휘발성 코딩

@sasca37

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요! 맞구독은 언제나 환영입니다^^