회원 도메인 개발

김슬기·2022년 10월 7일
0

애플리케이션 아키텍처

  • 계층형 구조 사용
    • controller, web : 웹 계층
    • service: 비즈니스 로직, 트랜잭션 처리(장고의 view)
    • repository: JPA를 직접 사용하는 계층, 엔티티 매니저 사용(장고의 view)
    • domain: 엔티티가 모여 있는 계층, 모든 계층에서 사용(장고의 medel)
  • 패키지 구조
    • domain
    • exception
    • repository
    • service
    • web
  • 개발 순서: 서비스, 리포지토리 계층을 개발하고, 테스트 케이스를 작성해서 검증, 마지막에 웹 계층 적용

회원 리포지토리 개발

  • repository/MemberRepository
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)//첫번째가 JPQL두번쨰가 반환타입
                .getResultList();          //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 요녀석은 매개변수를 바인딩하는 문법.setParameter("name", name)요것으로 name에 매개변수 name을 꽂는다.
    }
}
  • sql은 테이블을 대상으로 쿼리를 던지지만 jpql은 객체를 대상으로 쿼리를 던진다.

회원 서비스 개발

  • service/MemberService
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);//영속화 할떄 DB에 들어가기전에도 @id로만들어놓은 pk값은 항상 들어가잇따. 즉 스프링안에서 DB로 옮기기전에도 id 값은 들어가있다는의미
        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);
    } 
}
  • 영속화 할떄 DB에 들어가기전에도 @id로만들어놓은 pk값은 항상 들어가잇다.
    • 즉, 스프링안에서 DB로 옮기기전에도 id 값은 들어가있다는의미
  • JPA에서 값의 변경이나 수정등은 무조건 Transactional 안에서 일어나야함
    • readOnly=true : 데이터의 변경이 없는 읽기 전용 메서드에 사용, 영속성 컨텍스트를 플러시 하지 않으므로 약간의 성능 향상(읽기 전용에는 다 적용)
      • 단, 쓰기(수정,저장)에는 적용 x(변경불가가 된다)
    • @Transactional를 join메서드에 쓴 이유는 한번더 트랙젝션을 적고 옵션을 아무것도 주지않으면 디폴트값인 readOnly = false가 먹히므로 덮어써준다.
  • validateDuplicateMember에서 아무리 검증을 한다해도 동시에 검증받는 상황이 생기면 어쩔수없이 통과되어 DB에 중복저장될 수있다.
    • 그런경우 DB에서 속성 옵션에 unique를 넣어 중복 저장을 방지해야함
  • 멤버리포지토리를 위와 같이 작성하면 테스트나 이런것을 진행할때 수정이 불가능하다는 단점이 있긴함
    • 그래서 이렇게 바꾸면 수정가능해짐~

      MemberRepository memberRepository;
      
      @Autowired
      public void setMemberRepository(MemberRepository memberRepository) {
          this.memberRepository = memberRepository;
      }
    • 대신 단점은 테스트 이후에바꿀일이 없음에도 바꿀수있게 설정되어있음…

    • 궁극적으로 사용하는 방식은

      MemberRepository memberRepository;
      
      @Autowired
      public MemberService(MemberRepository memberRepository) {
          this.memberRepository = memberRepository;
      }
    • 이렇게 생성자에 주입시켜버림

      • 이러면 두 장점 모두 챙기기 가능 (set 불가, 테스트떄 원하는 리포지토리 주입가능)
    • 변경할 일 없으므로 memberRepository 선언할때 final로 선언하기

    • @Autowired
      public MemberService(MemberRepository memberRepository) {
      this.memberRepository = memberRepository;
      } 이녀석도 생략할 수있는것이 바로 lombok이용
      - @RequiredArgsConstructor을클래스에 입력하면 final선언된 녀석의 생성자를 만들어준다.

회원 기능 테스트

  • service/MemberServiceTest
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("kimseulgi");
        //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("kimseulgi");
        Member member2 = new Member();
        member2.setName("kimseulgi");
        //When
        memberService.join(member1); 
				try{
            memberService.join(member2); //예외가 발생해야 한다.
        }catch (IllegalStateException e){
            return;
        }
fail("예외가 발생해야 한다.");
    }
}
  • @RunWith(SpringRunner.class) : 스프링과 테스트 통합
  • @SpringBootTest : 스프링 부트 띄우고 테스트(이게 없으면 @Autowired 다 실패)
  • @Transactional : 반복 가능한 테스트 지원, 각각의 테스트를 실행할 때마다 트랜잭션을 시작하고 테스트가 끝나면 트랜잭션을 강제로 롤백 (이 어노테이션이 테스트 케이스에서 사용될 때만 롤백)
    • 롤백 해제하고싶으면 @Rollback(false)
  • em.flush() →영속성컨텍스트한것을 DB에 반영한다(쿼리를 날림)
    • 근데 결국 롤백되긴함 (Transactional에 의해
  • 테스트를할때 test/resources를 만들어서 여기에 새로운 applicational.yml을만들면
    • 실제개발과 설정을 다르게 가져갈 수 잇고

    • 새로운 DB로 저장하여 사용할 수 있다.

    • applicational.yml안에 디비 주소를 저걸로 바꾸면 H2 Db가 메모리 모드로 실행되어 테스트가능 >>>이러면 디비 안켜도 된다.

      spring:
        datasource:
          url: jdbc:h2:mem:test
    • 그런데 스프링은

      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
    • 이렇게 설정이 아무것도 없으면 애초에 메모리 모드로 DB를 실행

    • 어처피 테스트랑 개발과 따로 가져가므로 이렇게 설정을 분리하는게 좋다

profile
낭만그리고김슬기

0개의 댓글