jpa-spring boot 실습 - 3

김강현·2023년 4월 8일
0

SPRING-JPA-실습

목록 보기
3/5

Repository

@Repository
public class MemberRepository {
    @PersistenceContext
    private EntityManager em;

    public Long save(Member member){
        em.persist(member);
        return member.getId();
    }
    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();
    }

}

@PersistenceContext 를 넣어주어, EntityManager를 지정해준다.

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) {
        // 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 을 사용하여, 모든 메소드가 하나의 Transaction 에서 일어나도록 하기.

@Transactional 이 javax 하고 spring 둘다 제공하는데, spring 패키지로 사용할 것을 권장! (기능이 더 많음)

  • 영속성 컨텍스트의 flush를 조절한다던지,
  • readOnly 기능을 활용한다던지,

하는 여러 기능들 포함!!

@Transaction 의 경우 기본적으로 readOnly=false 이므로, join 같은 메소드 위에 어노테이션을 달아주면 된다.

MemberRepository Injection

spring bean에서 직접 -> set 메소드

MemberRepository 를 불러옴에 있어, @Autowired 를 사용하여 Spring Container bean 에서 지정할 수도 있지만,

  • 그렇게 하면, memberRepository injection 을 수정할 수가 없음
  • 그래서, setMemberRepository 를 만들어, 주입을 하는 방법으로
  • Test code 를 작성할때, mock을 직접 주입 가능.
// bean 에서 field 주입
@Autowired
private MemberRepository memberRepository;
// method 로 주입
private MemberRepository memberRepository;

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

-> constructor

그런데 사실상, run time 에 memberRepository 를 바꿀 일이 없음. 맨 처음에 결정 되면, 그걸로 계속 씀

그래서!! 권장사항 -> Constructor 에서 주입하기!!

private final MemberRepository memberRepository;

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

!! 요즘 버전의 spring은 @Autowired 를 넣어주지 않아도, 자동으로 인식하여 injection을 해줌.

-> @RequiredArgsConstructor (lombok)

더 나아가서, @RequiredArgsConstructor 을 활용하면, final 인 field를 알아서 Constructor에 해당시켜줌!

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

    private final MemberRepository memberRepository;

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

@Repository 의 경우에도, EntityManager 에 final 로 설정해주어 Constructor에서 주입받을 수 있다!
(lombok @RequiredArgsConstructor)

@Repository
@RequiredArgsConstructor
public class MemberRepository {
    
    private final EntityManager em;
...
}

@PersistenceContext 가 쓰여야 하지만, @Autowired 로 대체 될 수 있도록, 스프링 부트에서 지원

  • @Autowired 가 생략 + @requiredArgsConstructor 로 final field 로 대체!

테스트 코드

@Test
public void __() throws Exception {
   //given
   
   //when
   
   //then
}

ctrl + shift + t test code 생성해주는 단축키
tdd 자동완성 저번에 만들어두었던, tdd 로 쉽게 생성 가능! (Live Templates)

회원가입 성공 (assertEquals)

@RunWith(SpringRunner.class) // spring boot 연결
@SpringBootTest // spring boot 연결
@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 saveId = memberService.join(member);

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

위 코드가 실행되면, pass가 된다. (이것저것 sql 문을 날리면서)

하지만, DB 를 보면 Member Table은 비어있다. why?

Test 에서 사용되는 @Transactional 엔 기본적으로 rollback 기능이 있다.
그런데 코드를 보면, insert 쿼리가 나가는 것 조차 하지를 않음!
이 쿼리문을 확인해보고 싶은데 어떻게 해야하나?

따로, Entity Manager를 불러와서, flush를 진행해주면 된다!!

@Autowired EntityManager em;
...
// when
Long saveId = memberService.join(member);
em.flush();
...

따라서, 따로 @Rollback(false) 어노테이션을 달아주어야 함.

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

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

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

실질적으로 가장 좋은 테스트 방법은, 따로 메모리 DB에서 테스트를 하는 것! (왓슨)

중복 회원 예외 (fail)

@Test
public void 중복_회원_예외() throws Exception {
   // given
   Member member1 = new Member();
   member1.setName("kim");
   Member member2 = new Member();
   member2.setName("kim");
  
   // when
   memberService.join(member1);
   try{
      memberService.join(member2); // 예외가 발생해야 한다!!
   } catch(IllegalStateException e){
      return;
   }

   // then
   fail("예외가 발생해야 한다. Test 실패");
}

이렇게 예외처리를 받아야하는 Test의 경우, expected 로 설정 가능

@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("예외가 발생해야 한다.");
 }

DB 연결 없이, Memory DB 로 Test 돌리기


test 폴더에 resources 디렉토리 만들면, Test 는 해당 폴더 내의 자원을 먼저 접근함.
즉, DB를 따로 쓸 수 있게됨.

h2에서 제공해주는 메모리 모드를 활용!

실제 < application.yml >

spring:
  datasource:
    url: jdbc:h2:tcp://localhost/~/jpashop_230403 # ;MVCC=TRUE : 권장
    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

Test - resources 의 < application.yml >

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

jdbc:h2:mem:test 를 사용해서, 테스트는 h2 를 메모리모드로 띄워서 활용!

기존 h2 서버를 꺼놔도, 잘 돌아감!!

Test의 경우, Spring boot에서는 비어있으면 메모리 모드로 지원을 해줌 (이렇게 해도 됨)

기본 설정은 create-drop 이다!!

  • 설정을 따로 가져가는 것!!
profile
this too shall pass

0개의 댓글