3 분 소요

회원 도메인 개발

  • 구현 기능
    • 회원 등록
    • 회원 목록 조회
  • 순서
    • 회원 엔티티 코드 다시 보기
    • 회원 리포지토리 개발
    • 회원 서비스 개발
    • 회원 기능 테스트

엔티티 코드는 이전에 작성하였으니 회원 리포지토리 코드를 작성해 볼 것이다.

회원 리포지토리 개발

회원 리포지토리 코드

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 // Spring에서 제공하는 어노테이션으로 컴포넌트 스캔(Component Scan) 의 대상이 되어 자동으로 스프링 빈으로 관리되게 됨
public class MemberRepository {

    @PersistenceContext // JPA가 제공하는 표준 어노테이션 이 어노테이션이 있으면 스프링이 생성한 JPA entity manager를 주입해줌
    // 만약 이 어노테이션 사용하지 않고 순수하게 JPA를 사용하면
    // EntityManagerFactory에서 직접 EntityManager를 꺼내서 써야함
    private EntityManager em; // 스프링이 Entity manager를 만들어 여기에 주입해줌

    public void save(Member member) {
        em.persist(member); // member를 저장
        // em.persist 하면 일단 영속성 컨텍스트에 member 엔티티를 넣고
        // 그리고 나중에 transaction이 commit 되는 시점에 DB에 insert 쿼리가 날라가 DB에 반영됨
    }

    public Member findOne(Long id) {
        return em.find(Member.class, id); // member 조회 (id에 해당하는 member 1개 조회)
    }
    // em.find는 단건 조회임 파라미터는 첫번째는 타입 두번째는 PK를 넣어주면 됨

    public List<Member> findAll() {
        // member 모두 조회 하고 싶은 경우 JPQL을 작성해야 (SQL과 유사하지만 차이가 존재)
        // SQL은 테이블을 대상으로 쿼리를 하지만
        // JPQL은 엔티티 객체에 대상으로 쿼리를 한다.
        // 결국 JPQL이 SQL로 번역되어야함
        return em.createQuery("select m from Member m", Member.class)
                .getResultList();

        // from의 대상이 테이블이 아니라 entity 여기선 Member.class
    }

    public List<Member> findByName(String name) {
        // name 으로 member를 조회하는 경우
        return em.createQuery("select m from Member m where m.name = :name", Member.class)
                .setParameter("name", name)
                .getResultList();
    }
}


회원 서비스 코드를 작성해보자

회원 서비스 개발

회원 서비스 코드

package jpabook.jpashop.service;

import jpabook.jpashop.domain.Member;
import jpabook.jpashop.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
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) // 트랜잭션, 영속성 컨텍스트로 JPA의 어떤 모든 데이터 변경이나 로직들은 트랜잭션 안에서 실행되어야 하기 때문에
// readOnly = true 경우 데이터의 변경이 없는 읽기 전용 메서드에 사용하는 것으로
// 영속성 컨텍스트를 flush 하지 않기 때문에 약간의 성능 향상이 일어남 (읽기 전용엔 다 적용)
// DB 드라이버가 지원하면 DB에서 성능 향상
@RequiredArgsConstructor // final 있는 필드만 가지고 생성자 만들어 줌
public class MemberService {
    private final MemberRepository memberRepository; // 생성자 주입 방법

    /*
    @Autowired // 스프링이 스프링 빈에 등록되어 있는 MemberRepository를 Injection 해줌 -> Field Injection
    private MemberRepository memberRepository;
     */
    // 사실 @Autowired를 이용한 Injection 방법 (Field Injection)에는 단점이 많다
    // 변경이 어렵다는 문제 등
    // 따라서 생성자 Injection 많이 사용, 생성자가 하나면 생략 가능하다

    /*
    private final MemberRepository memberRepository;
    // final 해두면 생성자 내에서 값 세팅 안하면 에러 터트림 -> 컴파일 시점에 memberRepository를 설정하지 않는 오류 체크 가능
    // (보통 기본 생성자를 추가할 때 발견)

    @Autowired // 요즘은 @Autowired 생략가능 <- 생성자가 하나만 있는 경우
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
     */

    /*
    // 롬북을 이용하면 생성자 작성 안해도 되는 장점

    @RequiredArgsConstructor // final 있는 필드만 가지고 생성자 만들어 줌
    public class memberService {
        private final MemberRepository memberRepository;

        // 생성자 작성 생략 가능
     */
    
    // 회원 가입
    @Transactional // 읽기전용이 아닌 경우 readOnly = true 넣으면 DB의 데이터 변경이 일어나지 않음
    // class 전체에 @Transaction 걸면 기본적으로 클래스 내의 public 메서드에 적용됨
    // 메서드에 따로 @Transaction 걸면 해당 메서드는 따로 건 어노테이션을 우선으로 적용됨
    public Long join(Member member) {
        validateDuplicateMember(member); // 중복 회원 검증
        memberRepository.save(member);

        return member.getId();
    }

    public void validateDuplicateMember(Member member) {
        List<Member> findMembers = memberRepository.findByName(member.getName());
        // 만약 동시에 DB에 같은 회원명으로 회원가입하여 (멀티 쓰레드 등의 상황)
        // validateDuplicateMember 통과하게 되면 동시에 insert가 되어 문제가 될 수 있음
        // 따라서 DB에서 Member의 name을 unique로 제약 조건을 걸어야 한다

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

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

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