mojo's Blog

회원 도메인 개발 본문

Spring

회원 도메인 개발

_mojo_ 2022. 9. 2. 16:58

 

회원 리포지토리 개발

 

구현 기능

- 회원 등록

- 회원 목록 조회

 

순서

- 회원 리포지토리 개발

- 회원 서비스 개발

- 회원 기능 테스트

 

@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 : 엔티티 메니저 주입이 자동으로 될 수 있도록 한다.

 

 

회원 서비스 개발

 

@Service
public class MemberService {

    @Autowired
    private MemberRepository memberRepository;

    /**
     * 회원 가입
     */
    @Transactional
    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("이미 존재하는 회원입니다.");
        }
    }

    /**
     * 회원 전체 조회
     */
    @Transactional(readOnly = true)
    public List<Member> findMembers() {
        return memberRepository.findAll();
    }

    @Transactional(readOnly = true)
    public Member findOne(Long memberId) {
        return memberRepository.findOne(memberId);
    }
}

 

- @Transactional(readOnly = true) : 데이터를 조회할 때는 readOnly 를 true 로 지정하여 읽기에 대한

   성능을 최적화하여 더 빠르게 읽을 수 있도록 설정할 수 있다. (기본값은 false)

- @Autowired : memberRepository 에 대한 스프링 빈을 의존관계 주입을 자동으로 해준다.

 

위 코드는 메서드에 직접 트랜잭션 애너테이션을 부여하였다.

그러나 클래스에 직접 애너테이션을 부여하여 설정할 수 있다.

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

    @Autowired
    private MemberRepository memberRepository;

    /**
     * 회원 가입
     */
    @Transactional
    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);
    }
}

 

join 메서드에는 조회가 아닌 삽입이 이뤄지기 때문에, @Transactional 을 붙여서 readOnly 의 값이

false가 될 수 있도록 설정하였다.

 

private final MemberRepository memberRepository;

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

 

위와 같이 생성자 주입을 하여 테스트 코드를 작성할 때 명시적으로 memberRepository 를 주입해줘야

한다는 것을 알 수 있다.

 

@RequiredArgsConstructor
public class MemberService {

    private MemberRepository memberRepository;

 

사실 의존관계를 주입할 때 가장 좋은 방법은 롬북을 활용하여 @RequiredArgsConstructor 를 붙이면 된다.

알아서 MemberService 에서 memberRepository 에 필요한 의존관계를 주입해줄 것 이다.

 

 

회원 기능 테스트

 

※ 테스트 요구사항

 

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

 

[cmd + shift + t] 단축키를 누르면 테스트 코드를 알아서 작성해 줄 수 있다.

 

 

위와 같이 Run tests using 을 IntelliJ IDEA 로 설정해야 정상적으로 수행이 된다.

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class MemberServiceTest {

    @Autowired
    private MemberService memberService;

    @Autowired
    private MemberRepository memberRepository;

    @Test
    public void 회원가입() throws Exception {
        // given
        Member member = new Member();
        member.setName("mojo");

        // when
        Long savedId = memberService.join(member);

        // then
        assertEquals(member, memberRepository.findOne(savedId));
    }
    
    ...
}

 

정상적으로 수행되었다.

 

이번엔 중복 회원 가입에 대한 테스트 코드를 작성해보도록 하자.

@Test
public void 중복_회원_가입() throws Exception {
    // given
    Member member1 = new Member();
    member1.setName("mojo");

    Member member2 = new Member();
    member2.setName("mojo");

    // when
    memberService.join(member1);
    try {
        memberService.join(member2);
    } catch (IllegalStateException e){
        return;
    }

    // then
    fail("예외가 발생해야 한다!");
}

 

여기서 try - catch 문을 활용하여 예외를 확인한다.

그러나 위 코드를 다음과 같이 간결하게 작성할 수 있다.

@Test(expected = IllegalStateException.class)
public void 중복_회원_가입() throws Exception {
    // given
    Member member1 = new Member();
    member1.setName("mojo");

    Member member2 = new Member();
    member2.setName("mojo");

    // when
    memberService.join(member1);
    memberService.join(member2);
    
    // then
    fail("예외가 발생해야 한다!");
}

 

테스트를 할 때 데이터베이스에 직접 데이터를 넣고 초기화하는 작업을 수행하는 것은 비효율적이다.

따라서 이를 해결하기 위해 메모리 공간 내에서 해결할 수 있도록 하는 방법이 존재한다.

우선 test 패키지 아래에 resources 를 생성하고 application.yml 을 동일하게 넣어준다.

 

 

그 다음에 datasource 의 url 을 다음과 같이 변경해준다.

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 띄우지 않고 수행이 된다.

그러나 더 좋은 방법은 spring 의 datasource 및 jpa 를 주석처리 하는 방법이다.

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

 

'Spring' 카테고리의 다른 글

주문 도메인 개발  (0) 2022.09.06
상품 도메인 개발  (0) 2022.09.02
애플리케이션 구현 준비  (2) 2022.09.02
도메인 분석 설계  (0) 2022.08.08
빈 스코프  (0) 2022.07.29
Comments