회원 도메인 개발

OneTwoThree·2023년 8월 6일
0

출처


리포지토리

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

@Repository 스프링 빈으로 등록
@PersistenceContext 애노테이션에 의해 스프링이 EntityManager를 만들어서 주입해준다.
findAll, findByName은 JPQL을 사용한다.

서비스

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

    private final MemberRepository memberRepository;

    // 회원 가입
    @Transactional(readOnly = false)
    public Long join(Member member){
        validateDuplicateMember(member);
        memberRepository.save(member);
        return member.getId();
    }

    private void validateDuplicateMember(Member member) {
        //EXCEPTION
        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 기본적으로 트랜잭션 안에서 데이터를 변경해야 한다.
클래스 레벨에 @Transactional 애노테이션을 쓰면 public 메소드들에는 기본적으로 트랜잭션이 걸린다.

@Transactional에는 readOnly 속성이 있다.
readOnly = true : JPA가 읽기전용으로 성능을 최적화해준다. 읽기에는 가급적 넣어주는게 좋다
readOnly = false : 읽기전용이 아니다. 데이터를 쓸 수 있다.

위 예시와 같이 클래스단에는 readOnly=false로 놓고 (읽기전용인 메소드가 많기 때문에) 읽기전용이 아닌 메소드에 readOnly=true로 설정해주면 메소드에 달린 애노테이션이 우선순위를 갖는다.

@RequiredArgsConstructer로 final이 있는 필드에 대해서만 생성자를 만든다. 스프링은 생성자가 하나만 있는 경우 자동으로 생성자 주입을 해준다.

실무에서는 회원가입 중복 체크를 위해 DB의 member의 name을 unique 제약조건으로 거는 것이 좋다.

@Repository
@RequiredArgsConstructor
public class MemberRepository {
    
    private final 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();
    }
}

Spring Data Jpa를 사용하면 위와 같이 리포지토리에서 EntityManager에도 생성자 주입을 사용할 수 있다!(@Autowired 로 주입이 가능하게 해줌)

테스트 코드

요구사항

  • 회원 가입에 성공해야 한다
  • 회원 가입 시 같은 이름이 있으면 예외가 발생해야 한다
@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 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("예외가 발생해야 한다.");
        
    }

}

@RunWith(SprngRunner.class) 애노테이션은 Junit을 실행할 때 스프링과 엮어서 실행한다는 뜻이다.
@SPringBootTest 로 스프링을 띄운 채로 테스트한다. 이게 있어야 @Autowired 등이 동작한다.
@Transactional로 트랜잭션을 걸고 테스트를 돌리고, 테스트가 끝나면 롤백을 해주도록 한다. (테스트가 아닌 경우에는 롤백하지 않음)

@Transactional 이 있기 때문에 JPA에서 같은 트랙잭션 안에서 PK 값이 같으면 같은 영속성 컨텍스트에서 똑같이 관리된다.

테스트에서 @Transactional이 있기 때문에 회원가입 테스트의 경우 INSERT 쿼리가 나가지 않는 것을 확인할 수 있다.
then 절에서 flush 해서 쿼리는 보고 롤백도 할 수 있다
또는 @Rollback(false) 애노테이션을 붙여줄 수도 있다

fail()은 코드가 돌다가 fail 에 도착하면 테스트가 실패한다.
fail과 try , catch를 사용해서 예외 테스트를 할 수도 있다.
하지만 @Test(expected = IllegalStateException.class) 와 같이 작성하는 편이 더 깔끔하다.


메모리에서 테스트하기

테스트를 Memory DB를 사용해서 할 수 있다.

위와 같이 test 디렉토리에도 resources 디렉토리와 application.yml 파일을 만들면 테스트 할 때는 이 설정파일을 사용하게 된다.

spring: #띄어쓰기 없음
  datasource: #띄어쓰기 2칸
      url: jdbc:h2:mem:test #4칸
      username: sa
      password:
      driver-class-name: org.h2.Driver
  jpa: #띄어쓰기 2칸
      hibernate: #띄어쓰기 4칸
        ddl-auto: create #띄어쓰기 6칸
      properties: #띄어쓰기 4칸
        hibernate: #띄어쓰기 6칸
  # show_sql: true #띄어쓰기 8칸
          format_sql: true #띄어쓰기 8칸
logging.level: #띄어쓰기 없음
  org.hibernate.SQL: debug #띄어쓰기 2칸
  org.hibernate.type: trace #띄어쓰기 2칸

url 부분을 메모리를 사용하도록 바꿔준다 (h2 db에서 지원함)
이렇게 하면 h2 db를 내리고 테스트를 돌려도 테스트가 동작한다.

하지만 스프링부트에서는 아래와 같이

#spring: #띄어쓰기 없음
#  datasource: #띄어쓰기 2칸
#      url: jdbc:h2:mem:test #4칸
#      username: sa
#      password:
#      driver-class-name: org.h2.Driver
#  jpa: #띄어쓰기 2칸
#      hibernate: #띄어쓰기 4칸
#        ddl-auto: create #띄어쓰기 6칸
#      properties: #띄어쓰기 4칸
#        hibernate: #띄어쓰기 6칸
#  # show_sql: true #띄어쓰기 8칸
#          format_sql: true #띄어쓰기 8칸
logging.level: #띄어쓰기 없음
  org.hibernate.SQL: debug #띄어쓰기 2칸
  org.hibernate.type: trace #띄어쓰기 2칸

db 관련 설정드을 지워버리면 메모리 모드로 돌려버린다

0개의 댓글