본문 바로가기

[JPA] 회원 도메인

@cayman0312025. 12. 2. 11:43

회원 도메인 부분을 개발하며 JPA에서 사용하는 기술에 대해 학습해보자.

회원 리포지토리

@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();
    }
}
  • @Repository: 스프링 빈으로 등록, JPA예외를 스프링 기반 예외로 변환한다.

회원 서비스

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

    private final MemberRepository memberRepository;

    /*
    * 회원 가입
    * */
    @Transactional
    public Long join(Member member) {
        validateDuplicateMember(member); //중복 회원 검증
        memberRepository.save(member);
        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 findMember(Long memberId) {
        return memberRepository.findOne(memberId);
    }

}
  • @Service: 스프링 빈으로 등록
  • @Transactional: 트랜잭션, 영속성 컨텍스트
    • 'readOnly=true`: 데이터의 변경이 없는 읽기 전용 메서드에 사용한다. 영속성 컨텍스트를 플러시 하지 않으므로 성능 향상이 이루어진다.
    • 데이터베이스 드라이버가 지원하면 DB에서의 성능이 향상된다.

실무에서는 검증 로직이 존재하여도 멀티 스레드 상황을 고려하여 회원 테이블의 회원명 컬럼에 유니크 제약 조건을 걸어주는 것이 안전하다.

@Autowired와 같은 스프링 필드 주입 보다는 생성자 주입을 사용하는 것이 안전하다. 이때 롬복 라이브러리와 스프링 데이터 JPA를 이용하여 @RequiredArgsConstructor를 사용할 수 있다. 이를 사용하면 EntityManager도 주입이 가능하다.

회원 기능 테스트

@ExtendWith(SpringExtension.class)
@SpringBootTest
@Transactional
class MemberServiceTest {

    @Autowired MemberService memberService;
    @Autowired MemberRepository memberRepository;
    @Autowired EntityManager em;

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

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

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

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

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

        //when
        memberService.join(member1);

        //then
        assertThrows(IllegalStateException.class, () -> {
            memberService.join(member2); // 중복 회원 가입 시도
        }, "IllegalStateException 예외가 발생해야 합니다.");
    }
}

junit5를 사용하여 테스트를 진행하였다. 테스트에서 요구하는 상황 다음과 같다.

  • 회원 가입이 성공하여야 한다.
  • 회원 가입시 중복된 이름이 있으면 예외가 발생하여야 한다.
  • @ExtendWith(SpringExtension.class): 스프링과 테스트를 통합한다.
  • @SpringBootTest: 스프링 부트를 띄우고 테스트를 진행한다. 이 애노테이션이 없으면 주입이 이루어지지 않아 테스트가 실패한다.
  • @Transactional: 반복가능한 테스트를 지원한다. 각각의 테스트를 실행할 때 마다 트랜잭션을 시작하고 테스트가 끝나면 강제로 롤백한다.(단, 이 애노테이션이 테스트 케이스에서 사용될 때만 롤백이 진행된다.)

관련 기술

JPLQ

SQL과 매우 흡사하다. 그러나 SQL은 테이블 대상으로 쿼리를 진행하는 반면, JPLQ는 엔티티 객체를 대상으로 쿼리를 진행한다.

JPA의 롤백

JPA는 기본적으로 롤백을 하여 flush 처리를 한다. 따라서 테스트를 진행하여 보면 로그에 insert문이 안 찍히는 것을 알 수 있다. 만약 롤백이여도 로그상에서 이러한 내용을 모두 확인 하고 싶다면 em.flush() 코드를 추가하여 확인할 수 있다.

테스트 케이스를 위한 설정

테스트 케이스는 격리된 환경에서 실행하고 끝나면 데이터를 초기화하는 것이 좋다. 따라서 서버에 이를 올리기 보다는 메모리 DB를 활용하여 진행하는 것이 가장 이상적이다.

이를 위해 test/resources 패키지에 application.yml와 같은 설정 파일을 생성하여 테스트를 진행하는 것이 좋다. 이를 진행하면 이제 테스트에서 테스트 케이스를 실행하면 이 위치에 있는 설정 파일을 이용하여 테스트를 진행한다.

스프링 부트는 기본적으로 datasource 설정이 없으면 메모리 DB를 사용하고, driver-class도 현재 등록된 라이브러리를 보고 찾아가 준다. 추가로 ddl-auto역시 create-drop모드로 동작한다. 따라서 데이터 소스나, JPA 관련 추가 설정을 할 필요가 없다.

 

'Develop > Spring' 카테고리의 다른 글

[Spring] 코드 리팩토링 - 계층 분리  (1) 2025.12.17
[JPA] 변경 감지와 병합(merge)  (0) 2025.12.02
[JPA] 도메인 분석 설계  (0) 2025.12.02
[MVC] 요청 매핑  (0) 2025.12.02
[MVC] 핸들러 매핑과 핸들러 어댑터  (0) 2025.12.02
cayman031
@cayman031 :: 그누로그

목차