솜이의 데브로그

[Spring Boot] 회원 도메인 개발 본문

dev/Spring Boot

[Spring Boot] 회원 도메인 개발

somsoming 2021. 11. 10. 23:53

Reference : Inflearn- 실전 스프링부트와 JPA 활용1 (김영한님 강의)

 

 

다음 기능들을 포함한 애플리케이션을 개발해보자.

  • 회원 기능
    • 회원 등록
    • 회원 조회
  • 상품 기능
    • 상품 등록
    • 상품 수정
    • 상품 조회
  • 주문 기능
    • 상품 주문
    • 주문 내역 조회
    • 주문 취소

 

 

회원 리포지토리

 

MemberRepository.java

 

  • @Repository : 스프링빈으로 등록하고, JPA 예외를 스프링 기반 예외로 변환
  • @PersistenceContext : 엔티티 매니저 주입
  • @PersistenceUnit : 엔티티 매니저 팩토리 주입

 

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();
    }

}
  • save() : persist로 멤버 엔티티 넣고, 트랜잭션이 커밋되는 시점에 db에 반영됨. (insert query가 날라감)
  • findOne() : 단건 조회. 멤버를 조회해서 반환
  • findAll() : JPQL을 사용해서 query 생성. (SQL과 비슷한데 FROM의 대상이 테이블이 아닌 엔티티가 된다.)
  • findByName() : Parameter binding해서 특정 이름의 회원만 찾는다.

 

 

회원 서비스

 

service package / MemberService.java

 

  • @Service : Component scan의 대상이 되어 자동으로 등록됨.
  • @Transactional : 트랜잭션, 영속성 컨텍스트.
    • readOnly=true : 데이터 변경이 없는 읽기 전용 메서드에 사용한다. 약간의 성능 향상 (읽기 모드만!!!)
    • 데이터베이스 드라이버가 지원시 DB에서 성능 향상
    • 기본적으로 두면 readOnly=false 이므로 필요한 상황에 따라 사용
  • @Autowired : 생성자 Injection 많이 사용
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()) {
            //EXCEPTION
            throw new IllegalStateException("이미 존재하는 회원입니다.");
        }
    }

    //회원 전체 조회
    public List<Member> findMembers(){
        return memberRepository.findAll();
    }

    //회원 단건 조회
    public Member findOne(Long memberId){
        return memberRepository.findOne(memberId);
    }

}
  • 중복 검증 시 실무에서는 멀티 쓰레드 상황을 고려하여 회원 테이블의 회원명 컬럼에 유니크 제약을 추가하는 것이 안전하다.

 

 

회원 기능 테스트

  • 회원 가입에 성공해야함
  • 회원 가입 시 같은 이름이 있으면 예외 발생
  • @RunWith(SpringRunner.class) : 스프링과 테스트 통합.
  • @SpringBootTest : 스프링 부트를 띄운 상태에서 테스트 실행. (있어야 @Autowired 실행)
  • @Transactional : 테스트 실행할때마다 트랜잭션 시작하고, 테스트 끝나면 트랜잭션을 강제로 롤백한다. 반복 가능한 테스트 지원.
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 javax.persistence.EntityManager;

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;
    @Autowired EntityManager em;

    @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("예외가 발생해야 한다.");
    }

}

★ 테스트 케이스 실행 시, Gvien, When, Then 사용해서 각각의 케이스 설정해서 작성하면 편하다!

 

 

test/resources/application.yml

spring:

logging:
  level:
    org.hibernate.SQL: debug
    org.hibernate.type: trace

파일에 원하는 설정대로 하면 메모리 DB를 따로 사용하여 격리된 환경에서 테스트를 실행할 수 있다.

테스트 케이스를 위한 스프링 환겨과, 일반적으로 애플리케이션을 실행하는 환경이 다르므로 설정 파일을 다르게 사용하자.

 

위의 파일을 설정하면 테스트에서 스프링을 실행 시 이 위치에 있는 설정 파일을 읽는다.

(이 위치에 없으면 src/resources/application.yml 을 읽는다.)