본문 바로가기

Spring/웹 애플리케이션 개발

04. 회원 도메인 개발

💡 본 게시글은 김영한님의 인프런(Inflearn) 강의 실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발에 대해 공부하고, 정리한 내용입니다.

1. 회원 도메인 개발

1) 회원 리포지토리 개발

(1) 회원 리포지토리 코드

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

(2) 기술 설명

  • @Repository: 스프링 빈으로 등록하고, JPA 예외를 스프링 기반 예외로 변환합니다.
  • @PersistenceContext: EntityManager를 주입하여 사용합니다.
  • EntityManager: JPA는 스레드가 하나 생성될 때마다 EntityManagerFactory에서 EntityManager를 생성하며, 내부적으로 DB 커넥션 풀을 사용하여 DB와 연결됩니다.
  • @PersistenceUnit: EntityManagerFactory를 주입할 때 사용합니다.

(3) 기능 설명

  • save(): em.persist()를 통해 엔티티를 영속성 컨텍스트에 영속화합니다.
  • findOne(): 엔티티를 ID로 조회합니다.
  • findAll(): JPQL을 사용하여 모든 회원을 조회합니다.
  • findByName(): JPQL을 사용하여 특정 이름의 회원을 조회합니다.

2) 회원 서비스 개발

(1) 회원 서비스 코드

package jpabook.jpashop.service;

import jpabook.jpashop.domain.Member;
import jpabook.jpashop.repository.MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;

@Service
@Transactional(readOnly = true)
public class MemberService {

    @Autowired
    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);
    }
}

(2) 기술 설명

  • @Service: 서비스 계층을 정의합니다.
  • @Transactional: 트랜잭션과 영속성 컨텍스트를 관리합니다.
    • readOnly=true: 읽기 전용 메서드에 사용하여 성능을 최적화합니다. 데이터의 변경이 없는 조회에만 사용합니다.
  • @Autowired: 의존성을 주입합니다. 생성자 주입을 권장하며, 생성자가 하나일 경우 생략 가능합니다.

(3) 기능 설명

  • join(): 회원 가입 기능으로, 중복 회원을 검증한 후 저장합니다.
  • findMembers(): 모든 회원을 조회합니다.
  • findOne(): 특정 ID의 회원을 조회합니다.

참고:

  • 멀티 쓰레드 환경에서는 회원 테이블의 회원명 컬럼에 유니크 제약 조건을 추가하는 것이 안전합니다.
  • 필드 주입 대신에 생성자 주입을 사용하여 변경 불가능한 객체를 생성하는 것이 좋습니다.

3) 회원 기능 테스트

(1) 테스트 요구사항

  • 회원가입을 성공해야 한다.
  • 회원가입할 때 같은 이름이 있으면 예외가 발생해야 한다.

(2) 회원가입 테스트 코드

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 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;

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

(3) 기술 설명

  • @RunWith(SpringRunner.class): 스프링과 JUnit 통합 테스트를 실행합니다.
  • @SpringBootTest: 스프링 부트를 띄워 통합 테스트를 실행합니다.
  • @Transactional: 각 테스트 메서드가 실행될 때마다 트랜잭션을 시작하고, 테스트가 끝나면 롤백합니다. 이를 통해 반복 가능한 테스트 환경을 보장합니다.

(4) 기능 설명

  • 회원가입 테스트: 정상적으로 회원가입이 수행되는지 검증합니다.
  • 중복 회원 예외처리 테스트: 같은 이름의 회원이 가입될 경우 예외가 발생하는지 검증합니다.

참고: 테스트 케이스 작성에서 Given-When-Then 구조를 사용하면 테스트 코드를 더욱 직관적으로 작성할 수 있습니다.

(5) 테스트 케이스를 위한 설정

  • 테스트 환경에서 메모리 DB 사용: 테스트는 격리된 환경에서 실행하고 끝나면 데이터를 초기화하는 것이 좋습니다. 이를 위해 메모리 DB를 사용하는 것이 가장 이상적입니다.
  • 테스트용 설정 파일 추가: test/resources/application.yml 파일을 추가하여 테스트 환경을 설정합니다.
spring:
  datasource:
    url: jdbc:h2:mem:testdb
    username: sa
    password:
    driver-class-name: org.h2.Driver
  jpa:
    hibernate:
      ddl-auto: create
  properties:
    hibernate:
      show_sql: true
      format_sql: true
  open-in-view: false

logging.level:
  org.hibernate.SQL: debug

스프링 부트는 데이터소스 설정이 없을 경우 기본적으로 메모리 DB를 사용하고, 필요한 드라이버도 자동으로 설정해줍니다. ddl-autocreate-drop 모드로 동작합니다.


4. 총 정리

  1. 회원 리포지토리 개발
    • @RepositoryEntityManager를 사용하여 회원 데이터를 관리하는 리포지토리를 구현합니다. save(), findOne(), findAll(), findByName() 등의 메서드를 통해 CRUD 기능을 제공합니다.
  2. 회원 서비스 개발
    • @Service@Transactional을 사용하여 비즈니스 로직을 처리하고, 트랜잭션 관리를 수행합니다. join() 메서드를 통해 회원 가입 기능을 구현하고, 중복 회원 검증 로직을 추가하여 데이터 무결성을 유지합니다.
  3. 테스트 케이스 작성
    • @SpringBootTest@Transactional을 사용하여 테스트를 실행하고, 테스트가 끝나면 롤백하여 테스트 환경을 초기화합니다. 회원가입 테스트중복 회원 예외처리 테스트를 통해 비즈니스 로직이 정상적으로 작

동하는지 검증합니다.

  1. 기술 스택과 설정 이해
    • @Autowired와 같은 의존성 주입 방식에서 생성자 주입을 권장하며, 생성자 주입 방식을 사용하면 불변 객체를 생성할 수 있습니다. 또한, 스프링 환경에서 JPA트랜잭션 관리를 제대로 이해하고 설정할 수 있어야 합니다.
  2. 테스트 환경 설정과 데이터 격리
    • 테스트는 실제 환경과 격리된 상태에서 진행되어야 하며, 메모리 DB를 사용하여 테스트 데이터를 격리할 수 있습니다. test/resources/application.yml 파일을 통해 테스트 환경을 분리하여 설정하는 방법을 이해합니다.

위 내용을 바탕으로, 스프링 부트와 JPA를 활용한 회원 도메인 개발의 핵심 개념과 실무 적용 방법을 익힐 수 있으며, 안정적이고 유지보수하기 쉬운 애플리케이션을 개발할 수 있습니다.

'Spring > 웹 애플리케이션 개발' 카테고리의 다른 글

06. 주문 도메인 개발  (0) 2024.09.08
05. 상품 도메인 개발  (0) 2024.09.08
03. 애플리케이션 구현 준비  (2) 2024.09.08
02. 도메인 분석 설계  (2) 2024.09.08
01. 프로젝트 환경설정  (0) 2024.09.08