[실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발] 회원 도메인 개발

이재표·2023년 10월 28일
0

서비스 개발은 리포지토리 -> 서비스 -> 컨트롤러 순서로 개발을 진행하고 회원에 대한 기능 개발을 진행해 보자!!

Repository

@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.username=:name")
                .setParameter("name", name)
                .getResultList();
    }
}

기본 JPA와 달리 @PersistenceContext 어노테이션을 이용하여 바로 EntityManager를 생성할수 있다. 이때 jpql의 경우 테이블명이 아닌 엔티티 자체를 통해 sql을 작성할수 있으며 @Repository 어노테이션을 이용하여 해당 Repository를 스프링 빈으로 스프링 컨텍스트에 등록시켜준다. 해당 어노테이션에 커포넌트가 있어서 컴포넌트 스캔의 대상이 되어 스프링 빈이 된다.

Service

@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) {
        List<Member> findMembers = memberRepository.findByName(member.getUsername());
        if(!findMembers.isEmpty()){
            throw new IllegalArgumentException("이미 존재하는 회원입니다.");
        }
    }

    public List<Member>findMembers(){
        return memberRepository.findAll();
    }
    public Member findOne(Long memberId){
        return memberRepository.findOne(memberId);
    }
}
  • 서비스 레이어의 메서드들은 각각 트랜잭션 내에서 작동해야하기 때문에 @Transactional 어노테이션을 추가해줘야한다. 이때 옵션으로 readOnly=true를 넣게되면 읽기 전용 트랜잭션으로 조회만 하는 메서드에서 좀 더 최적화될수 있다.

  • @Autowired를 사용한다면 스프링 컨텍스트에 저장된 스프링 빈을 불러와 생성자 주입해주는 어노테이션으로 Repository의 경우 @Repository어노테이션으로 스프링 컨텍스트에 저장해준것을 알수 있다. 하지만 해당 방식은 조금 문제가 테스트코드를 작성하거나 할때 변경이 불가하다 때문에 setter injection을 하기도 한다. mock을 직접 주입이 가능하다. 하지만 이것도 문제가 있어서 결국 생성자 주입을 사용해야한다. 이때 @RequiredArgsConstructor을 사용하면 final이 붙어있는 값에 생성자 주입을 해주게 된다.

  • validateDuplicateMember()메서드의 경우 검증이 안될수 있을거라 생각하는 경우도 있을수 있다. 왜냐하면 was가 동시에 여러개가 뜨게되면 동시에 같은 값이 DB에 호출되면 동시에 같은 이름이 회원가입되기 때문에 실무에서는 멀티스레드를 고려하여 데이터베이스에 멤버의 네임을 unique제약조건으로 걸어주는것이 안전하다.

Test Code

회원가입 테스트
중복 회원 예외처리 테스트

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
class MemberServiceTest {
    @Autowired
    MemberService memberService;
    @Autowired
    MemberRepository memberRepository;
    @Test
    void 회원가입()throws Exception{
        //given
        Member member = new Member();
        member.setUsername("kim");

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

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

    @Test
    void 중복_회원_예약()throws Exception{
        //given
        Member member1 = new Member();
        member1.setUsername("kim1");
        Member member2 = new Member();
        member2.setUsername("kim1");

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

        //then
        Assert.fail("예외가 발생해야 한다.");
    }
}
  • @RunWith(SpringRunner.class) : junit실행할때 스프링과 함께 실행한다는 의미

  • @SpringBootTest : 스프링 부트 띄운상태로(스프링 컨테이너안에서) 테스트(이게 없으면 @Autowired 다 실패)

  • @Transactional : 반복 가능한 테스트 지원, 각각의 테스트를 실행할 때마다 트랜잭션을 시작하고 테스트가 끝나면 트랜잭션을 강제로 롤백 (이 어노테이션이 테스트 케이스에서 사용될 때만 롤백)

  • 실제 외부 db를 사용하는게 아닌 테스트를 완전 격리된 환경, java를 띄울때 java안에 살짝 데이터베이스를 만들어 띄우는 방법 Memory DB를 사용하는 방법

    test폴더에 resources폴더를 만들고 안에 application.yml을 만들어준다. 이렇게하면 test를 돌릴때는 main의 application.yml을 무시한다. 그리고 spring.datasource.url을 h2를 메모리 모드로 띄워준다.(jdbc:h2:mem:test) 하지만 스프링부트에서는 application.yml의 설정이 없으면 기본적으로 메모리모드로 돌려버린다.

    운영, 개발, 테스트 모두 설정을 다르게 가져가는것이 맞다. 각 환경마다 ddl모드도 다르고 때에따라 모킹해야할수도 있고 등등 환경이 다르기때문에 다르게 설정을 가져가야한다.

0개의 댓글