[Likelion] Spring 스터디 4주차-2

99winnmin·2022년 8월 4일
0

likelion

목록 보기
5/5

강의 : Inflearn-실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
기간 : 7/28~8/3

애플리케이션 구현 준비

  • 회원 기능
    • 회원 등록
    • 회원 조회
  • 상품 기능
    • 상품 등록
    • 상품 수정
    • 상품 조회
  • 주문 기능
    • 상품 주문
    • 주문 내역 조회
    • 주문 취소

예제를 단순화 하기 위해 다음 기능은 구현X
로그인과 권한 관리X
파라미터 검증과 예외 처리X
상품은 도서만 사용
카테고리는 사용X
배송 정보는 사용X


애플리케이션 아키텍쳐

계층형 구조 사용

  • controller, web: 웹 계층
  • service: 비즈니스 로직, 트랜잭션 처리
  • repository: JPA를 직접 사용하는 계층, 엔티티 매니저 사용
  • domain: 엔티티가 모여 있는 계층, 모든 계층에서 사용

패키지 구조

  • jpabook.jpashop
    • domain
    • exception
    • repository
    • service
    • web

개발 순서: 서비스, 리포지토리 계층을 개발하고, 테스트 케이스를 작성해서 검증, 마지막에 웹 계층 적용


회원 도메인 개발

구현 기능

  • 회원 등록
  • 회원 목록 조회

순서

  • 회원 엔티티 코드 다시 보기
  • 회원 리포지토리 개발
  • 회원 서비스 개발
  • 회원 기능 테스트

회원 리포지토리 개발

  • MemberRepository.java
@Repository // spring bean 으로 등록
public class MemberRepository {

    @PersistenceContext // 엔티티 메니저( EntityManager ) 주입
    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(){ // 전체 조회
        // sql은 테이블을 대상으로 하지만 JPQL은 class를 대상으로 함
        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 예외를 스프링 기반 예외로 예외 변환
  • @PersistenceContext : 엔티티 메니저( EntityManager ) 주입
  • @PersistenceUnit : 엔티티 메니터 팩토리( EntityManagerFactory ) 주입

회원서비스 개발

  • MemberService.java
@Service
@Transactional(readOnly = true) // 읽기 전용으로 선언함으로써 성능 최적화, 데이터 변경 안됨
//@AllArgsConstructor -> 생성자 injection 만들어주는 효과
@RequiredArgsConstructor // final 변수만 매개변수로하는 생성자 생성
public class MemberService {
    // field 방식 injection은 코드를 변경하기 어려움
    private final MemberRepository memberRepository;

    /*@Autowired // 생성자 injection 이 요즘 권장되는 방식임 + 생성자가 딱 하나만 있으면 @Autowired 없어도 빈으로 등록됨
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }*/

    /**
     * 회원가입
     */
    @Transactional // 따로 설정한 것은 class 트랜젝션보다 우선권을 갖음
    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);
    }
}

기술 요약

  • @Service
  • @Transactional : 트랜잭션, 영속성 컨텍스트
    • readOnly=true : 데이터의 변경이 없는 읽기 전용 메서드에 사용, 영속성 컨텍스트를 플러시 하지 않으므로 약간의 성능 향상(읽기 전용에는 다 적용)
    • 데이터베이스 드라이버가 지원하면 DB에서 성능 향상
  • @Autowired : 생성자 Injection 많이 사용, 생성자가 하나면 생략 가능

참고: 실무에서는 검증 로직이 있어도 멀티 쓰레드 상황을 고려해서 회원 테이블의 회원명 컬럼에 유니크 제약 조건을 추가하는 것이 안전하다.

참고: 스프링 필드 주입 대신에 생성자 주입을 사용하자

필드주입

public class MemberService {
 @Autowired
 MemberRepository memberRepository;
}

생성자 주입

public class MemberService {
 private final MemberRepository memberRepository;
 public MemberService(MemberRepository memberRepository) {
 this.memberRepository = memberRepository;
 }
 ...
}
  • 생성자 주입 방식을 권장
  • 변경 불가능한 안전한 객체 생성 가능
  • 생성자가 하나면, @Autowired 를 생략할 수 있다.
  • final 키워드를 추가하면 컴파일 시점에 memberRepository 를 설정하지 않는 오류를 체크할 수 있다.(보통 기본 생성자를 추가할 때 발견)

회원 기능 테스트

테스트 요구사항
1. 회원가입을 성공해야 한다.
2. 회원가입 할 때 같은 이름이 있으면 예외가 발생해야 한다.

  • MemberServiceTest.java
@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
class MemberServiceTest {

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


    @Test
    @Rollback(false) // Transactional 이 Rollback 시켜버리는 것을 막음
    public void test_회원가입() throws Exception{
        //given : ~가 주어졌을 때
        Member member = new Member();
        member.setName("ryu");

        //when : 이러쿵 저러쿵 하면
        Long saveId = memberService.join(member);

        //then : 결과가 이렇다~
        em.flush(); // 영속성 컨텍스트에 있는 내용을 DB에 반영을 시킴
        assertEquals(member, memberRepository.findOne(saveId));
    }


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

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

        //when
        memberService.join(member1);

        //then
        IllegalStateException thrown = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
        assertEquals("이미 존재하는 회원입니다.", thrown.getMessage());
//        fail("예외가 발생해야 한다."); // 코드가 여기까지 오면 테스트 코드를 잘못 작성한 것이다!
    }
}

기술 설명

  • @RunWith(SpringRunner.class) : 스프링과 테스트 통합
  • @SpringBootTest : 스프링 부트 띄우고 테스트(이게 없으면 @Autowired 다 실패)
  • @Transactional : 반복 가능한 테스트 지원, 각각의 테스트를 실행할 때마다 트랜잭션을 시작하고 테스트가 끝나면 트랜잭션을 강제로 롤백 (이 어노테이션이 테스트 케이스에서 사용될 때만 롤백)

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

profile
功在不舍

0개의 댓글