Skip to content

[JPA study 4주차 이해림]

Haerim Lee edited this page May 5, 2024 · 1 revision

SECTION 4 회원 도메인 개발

(1) 회원 리포지토리 개발

핵심 도메인 비지니스 개발하기 @Repository 스프링에서 제공하는 어노테이션을 사용하면 자동으로 스프링 빈으로 관리가 됨.

@PersistenceContext 
private EntityManager em;

// 스프링이 EntityManager를 만들어줘서 여기에다가 주입, 인젝션을 해줌

 private void save(Member member) {
    em.persist(member); //persist를 하면 member 객체(엔티티)를 넣고 커밋되는 시점에 DB에 반영
}

//JPA가 얘를 저장하는 로직이 됨

public Member findOne(Long id) {
    return  em.find(Member.class, id);
}

// 조회하는 기능을 구성 중(단건조회), em.find는 JPA에서 제공하는 find를 사용해서 id값을 넘기면 멤버를 찾아서 반환해주는 역할을 함.

public List<Member> findAll() {
    List<Member> result = em.createQuery("select m from Member m", Member.class).getResultList();

    return result; 
}

//리스트 조회도 필요. findAll() 전부 다 찾아줘야 하니까 씀. em.createQuery를 사용해 JPQL를 작성해줘야 함. em.createQuery("JPQL", 반환타입) getResultList() 멤버를 리스트로 만들어줌.

참고로 JPQL은 SQL과 다름 기능적인 측면에선 다를 게 없지만, SQL은 테이블을 대상으로 쿼리 JPQL은 엔티티 객체를 대상으로 쿼리

public List<Member> findByName(String name) {
    return em.createQuery("select m from Member m where m.name = :name", Member.class).setParameter("name", name).getResultList();
}

// 이름으로 만약 검색을 하면 파라미터로 name을 넘김. JPQL를 짜는데 where문이 들어가게 함. 이렇게 되면 name 파라미터를 바인딩함.

(2) 회원 서비스 개발

@Service 스프링에서 제공하는 어노테이션

//회원 가입
public Long join(Member member) {

    validateDuplicateMember(member); //중복 회원 검증
    memberRepository.save(member);
    return member.getId();
}

private void validateDuplicateMember(Member member) {
    //EXCEPTION
}

//조인해서 멤버 객체를 넘기도록 함 memberRepository.save(member); 단순하게 멤버 넘기면 끝임. MemberRepository에 save부분 만들어 놓은 것이 있음. 근데 여기서 회원가입할 때 같은 이름인 회원이면 안된다는 로직을 넣어줌. (중복 회원이 있는 지 검증하는 로직) 중복회원이면 다음으로 못 넘어감 아니면 넘어가서 정상적으로 save하고 member.getId()로 아이디 반환 MemberRepository에 save부분에서 persist하게 되면 영속성 컨텍스트에 멤버 객체를 올려줌 그떄 영속성 컨텍스트는 키와 value가 있는데 키가 member 부분의 아이디 값이 됨. 그러면 아이디 값이 항상 생성이 되어 있는게 보장이 됨.

member부분의 아이디 값 @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "member_id") private Long id;

영속성 컨텍스트란 엔티티를 영구 저장하는 환경 어플리케이션과 DB사이에서 객체를 보관하는 가상의 DB같은 역할을 한다. key-value 형식으로 엔티티를 관리한다 key에는 해당 엔티티를 식별할 수 있는 식별자 값이 들어간다. 쉽게 멀해 ID value에는 엔티티

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 웬만한 JPA 언어는 트랜잭션안에서 처리가 되어야 해서 이러한 어노테이션 필요 (꼭 있어야 함) @Transactional(readOnly = true) 리소스하지 말고 단순하게 DB보고 이렇게 읽어라 할 때 이점을 봄 근데 읽는 코드말고 쓰는 코드에 readOnly = true를 넣으면 데이터 변경이 안되어서 절대 안됨

@Autowired
private MemberRepository memberRepository;

//이렇게 많이들 쓰지만 단점이 많이 보이는 코드 이유는? 엑세스할 방법이 없음 그리하여

private MemberRepository memberRepository;

@Autowired
public void setMemberRepository(MemberRepository memberRepository) {
    this.memberRepository = memberRepository;
}

//스프링에 바로 주입하는 방법말고 (세터익젝션) 테스트 코드를 작성할 때직접 주입가능 그 전 필드는 주입하기 까다로웠음 근데 치명적인 단점은 런타임에 누군가가 바꾸게 되면(이런 일은 대체로 없긴 함) 불변성을 확보하기 어려움

private final MemberRepository memberRepository;

@Autowired
public void MemberService(MemberRepository memberRepository) {
    this.memberRepository = memberRepository;

//요즘 뜨느 방법 생성자에서 인젝션하기 한번 생성할 때 중간에 셋해서 멤버리포지토리를 바꿀 수 없음 고로, 변경할 일이 없기에 final 를 써줌

이제 여기다라 롬복 적용 롬복에 @AllargsConstructor 필드 모든것을 가지고 생성자를 만들어줌 public void MemberService(MemberRepository memberRepository) { this.memberRepository = memberRepository; <-이것을 만들어 준다는 뜻 @RequiredArgsConstructor 얘는 파이널있는 필드만 가지고 생성자를 만들어 줌 (조금 더 나은)

MemberRepository.java에서도 @RequiredArgsConstructor 사용가능 스프링 부트를 사용하기 떄문에 (원래는 @PersistenceContext만 지원되는데 스프링 부트에서는 @Autowired도 지원해주기 때문)

(3) 회원 기능 테스트

테스트 코드로 검증하기 회원가입을 성공하고 같은 이름이 있으면 회원가입이 안돼야 함

먼저, 서비스 클래스에서 테스트를 작성할 것. (회원가입과 중복 회원 예외 테스트 코드)

@RunWith(SpringRunner.class) @SpringBootTest // 이 두 가지로 스프링과 인티그레이션 @Transactional // 데이터를 변경해야 하기 떄문에 이것으로 롤백

assertEquals - org.junit.Assert에 있는 것으로 검증하기 위해 사용

회원가입 테스트 코드를 작성 후 실행 근데 신가하게도 멤버만 셀렉트하고 인서트가 없음. -> savedId를 자세히 보면 엔티티매니저에 persist를 하지만 그렇다고 해서 DB에 인서트가 나가는 것이 아님. 커밋될 때만 인서트가 나감. 왜냐하면 스프링에서의 트랜잭션은 커밋을 안하고 롤백을 해줌. 그렇기에 @Rollback(false)으로 커밋하도록 유도해야 함.

em.flush(); 영속성 컨텍스트에 대한 변경과 등록을 데이터베이스에 반영

//given
    Member member = new Member();
    member1.setName("kim");

    Member member = new Member();
    member2.setName("kim");

    //when
    memberService.join(member1);
    memberService.join(member2); // 예외가 발생

그러나 이렇게 코드를 작성할 시 예외가 발생했음에도 then 부분으로 넘어가 제대로된 테스트 코드 실행 불가

    //when
    memberService.join(member1);
    try {
        memberService.join(member2); // 예외가 발생
    } catch (IllegalStateException e) {
            return;
    }

// 그리하여 이렇게 작성해야 함. 위 코드는 catch하여 IllegalStateException로 정상적으로 리턴하기에 테스트 코드를 잘 작동시킬 수 있음

이렇게 테스트 코드를 다 작성하였지만 테스트는 다 실행 후 데이터가 초기화되는 것이 가장 좋음 그래서 완적 테스트를 격리 된 환경, 자바 안에 띄울 때 살짝 데이터베이스를 띄어주는 방법이 있음 (메모리 디비를 사용하는 것) 스프링부트를 사용하면 공짜로 이것이 됨!

test파일 속 resources 디렉토리를 만들고 yml파일을 복사해옴 그러면 우선권을 가지고 테스트가 돌때는 여기 yml만이 우선권을 가지고 나머지는 무시됨 (yml 파일을 url: jdbc:h2:mem:test 로 코드 작성 안해도 됨 왜냐면 스프링부트는 별도의 설정이 없으면 메모리모드가 되기에)

실제로 테스트 yml과 운영 yml은 분리하는 것이 맞음

SECTION 5 상품 도메인 개발

(1) 회원 엔티티 개발 (비지니스 로직 추가)

item.java에 비지니스 로직 추가 (상품이 늘고 줄고를 보여야 함) 객체지향적으로 생각해보면 데이터를 가지고 있는 쪽이 비지니스 메서드를 가지고 있는 것이 응집력이 좋음

/**
 * stock 감소
 */
public void removeStock(int quantity) {
    int restStoke = this.stockQuantity - quantity;
    if (restStoke < 0) {
        throw new NotEnoughStockException("need more stock");
    }
    this.stockQuantity = restStoke;
}

// 재고가 0이 되면 재고가 아예없는 것이기에

섹션 5의 전반적인 내용은 4와 비슷하고 아주 간단한 로직.

Clone this wiki locally