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

밀크야살빼자·2023년 5월 9일
0

스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발 [김영한 강사님]
구현 기능

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

회원 레파지토리 개발

MemberRepository

package jpabook.jpashop.repository;

import jpabook.jpashop.domain.Member;
import org.springframework.stereotype.Repository;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;

@Repository //component 스캔에 의해 자동으로 빈으로 관리됨
public class MemberRepository {
    
    @PersistenceContext //스프링이 EntityMnager를 만들어서 em에 주입해줌
    private EntityManager em;
    
    @PersistenceUnit
    private EntityManagerFactory emf; // 직접 주입 -> 거의 사용할 일 없음
    
    public void save(Member member) {
    	//영속화 한다. -> 영속성 컨텍스트에 객체를 넣는다.
        em.persist(member); 
    }
    
    public Member findOne(Long id) {
    //id 값으로 멤버를 찾아서 리턴해준다. find(엔티티,기본키)
        return em.find(Member.class, id); 
    }
    
    public List<Member> findAll() {
    //em.createQuery(jpql,반환타입) -> 엔티티 객체를 대상으로 쿼리를 날림(sql은 테이블을 대상으로 쿼리를 날림)
    // jpql을 sql로 바꿔준다.
        return em.createQuery("select m from Member m", Member.class) 
            .getResultList(); //member를 리스트로 변환해줌
    }
    
    public List<Member> findByName(String name) {
        return em.createQuery("select m from Member m where m.name = :name", Member.class)
            .setParameter("name", name) //"name"은 위에 있는 name
            .getResultList();
    }
}
  • @Repository : 스프링 빈으로 등록, JPA 예외를 스프링 기반 예외로 예외 변환
    • DB 관리(연결, 해제, 자원 관리)
    • DB에 CRUD의 명령을 실행하게 만드는 인터페이스
    • '저장소'라는 뜻을 가진 단어로 JPA에서 Repository 인터페이스를 생성 후, JpaRepository<Entity, 기본키 타입>을 상속받으면(extends하면) 기본적인 Create, Read, Update, Delete가 자동으로 생성됨
  • @PersistenceContext : 엔티티 매니저(EntityManager)를 주입
    • EntityManager : JPA는 스레드가 하나 생성될 때마다 EntityManagerFactory에서 EntityManager를 생성한다.
    • EntityManager는 내부적으로 DB 커넥션 풀을 사용하여 DB와 연결된다.
  • @PsersistenceUnit : 엔티티 매니저 팩토리(EntityManagerFactory) 주입
  • save() : em.persist()는 영속성 컨텍스트를 통해서 엔티티를 영속화 한다.
  • findOne()
  • findAll() : JPQL을 호출하여 JPA에서 제공하는 메서드 호출만으로 작성할 수 없는 쿼리를 작성한다.
  • findByName

회원 서비스 개발

MemberService

package jpabook.jpashop.service;

import jpabook.jpashop.domain.Member;
import jpabook.jpashop.repository.MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;

@Service
@Transactional(readOnly = true) // 데이터 변경할때는 기본적으로 transactional이 있어야 함 -> 모든 데이터 변경은 트랜잭션 안에서 실행되어야 한다.
public class MemberService {
    
    @Autowired
    private final MemberRepository memberRepository; 
    // 변경할 일이 없기 때문에 final로 하는 것을 권장 -> final을 해주면 생성자에 값 세팅을 안해주면 에러떠서 확인할 수 있음
    
    public MemberSerivce(MemberRepository memberRepository){
    	this.memberRepository=memberRepository; //final을 썼을때 여기 부분을 작성 안해주면 에러 떠서 컴파일 시점을 확인할 수 있음
        }
    
    /**
	* 회원가입
	*/
    @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()) { //db에 이름이 있으면
            throw new IllegalStateException("이미 존재하는 회원입니다.");
        }
    }
    /**
	* 전체 회원 조회
	*/
    public List<Member> findMembers() {
        return memberRepository.findAll();
    }
    
    public Member findOne(Long memberId) {
        return memberRepository.findOne(memberId);
    }
}
  • @Service
    • 컨트롤러와 리포지토리 사이의 미들웨어
      미들웨어란?
      • 서로 다른 애플리케이션이 서로 통신하는데 사용되는 소프트웨어이다.
        -> 양쪽을 연결하여 데이터를 주고받을 수 있도록 중간에서 매개역할을 하는 소프트웨어, 네트워크를 통해서 연결된 여러 개의 텀퓨터에 있는 많은 프로세스들에게 어떤 서비스를 사용할 수 있도록 연결해주는 소프트웨어이다.
    • Model이 테이터페이스에서 받아온 데이터를 전달받아 가공하는 역할
    • DB 정보가 필요할 때는 Repository에게 요청
    • Controller에서 전달받은 사용자의 요청사항에 알맞게 데이터를 가공해서 데이터베이스로 전달하거나, 데이터베이스에서 데이터를 전달받아 가공하여 유저에게 전달하는 역할
  • @Transactional : 트랜잭션, 영속성 컨텍스트
    • readOnly=true : 데이터의 변경이 없는 읽기 전용 메서드에 사용, 영속성 컨텍스트를 플러시 하지 않으므로 약간의 성능 향상(읽기 전용에는 다 적용)
    • 데이터베이스 드라이버가 지원하면 DB에서 성능 향상
  • @Autowired
    • 생성자 Injection 많이 사용, 생성자가 하나면 생략 가능 -> set으로 해서 누군가 변경할 일이 없고 테스트케이스 작성할 때 주입해주어야 하는 것을 안 빼먹고 작성해 줄 수 있다.
      @Autowired
      private MemberRepository memberRepository;
      위와 같이 사용하면 테스트 등을 할때 변경해야하는데 필드에, private로 되어있어서 접근할 수가 없어서 변경할 수가 없다. -> 주입하기가 까다롭다.
      그래서 setter를 이용해서 주입해주는데 스프링이 바로 주입해주는 것이 아니라 파라미터를 통해서 주입시켜준다. 이 방식은 테스트 코드를 작성할때 직접 주입해줄수 있어서 좋다. 또한 runtime 돌아가는 시점에 누군가가 호출해서 변경할 수도 있다.
  • join()
  • findMembers()
  • findOne()

❗참고❗
실무에서는 검증 로직이 있어도 멀티 쓰레드 상황을 고려해서 회원 테이블의 회원명 컬럼에 유니크 제약 조건을 추가하는 것이 안전하다.
스프링 필드 주입 대신에 생성자 주입을 사용하자.

필드 주입

public class MemberService {
    
    @Autowired
    MemberRepository memberRepository;
    ...
}

생성자 주입

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

lombok

@RequiredArgsConstructor//final 있는 필드만 가지고 생성자를 만들어 준다.
public class MemberService {
    
    private final MemberRepository memberRepository;
    ...
}
  • 스프링 데이터 JPA를 사용하면 EntityManager도 주입 가능
@Repository
@RequiredArgsConstructor
public class MemberRepository {
    
    private final EntityManager em;
    ...
}

회원 기능 테스트

테스트 요구사항

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

회원가입 테스트 코드

package jpabook.jpashop.service;

import jpabook.jpashop.domain.Member;
import jpabook.jpashop.repository.MemberRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

@RunWith(SpringRunner.class) //Junit이랑 스프링이랑 같이 실행
@SpringBootTest // 스프링 부트를 띄운 상태에서 테스트
@Transactional //테스트 돌린 다음에 rollback
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 : 이렇게 된다
        em.flush(); // 영속성 컨텍스트에 있는 어떤 변경이나 등록 내용을 데이터베이스에 반영하는 것이다.
        assertEquals(member, memberRepository.findOne(saveId));
    }
    
    @Test(expected = IllegalStateException.class) //try 대신에 이렇게 써서 예외 작성 가능
    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("예외가 발생해야 한다."); // 
    }
}
  • @RunWith(SpringRunner.class) : 스프링과 테스트 통합
  • @SpringBootTest : 스프링 부트 띄우고 테스트(이게 없으면 @Autowired) 다 실패
  • @Transactional : 반복 가능한 테스트 지원, 각각의 테스트를 실행할 때마다 트랜잭션을 시작하고 테스트가 끝나면 드랜잭션을 강제로 롤백(이 어노테이션이 테스트 케이스에서 사용될 때만 롤백)
  • given - when - then -> 필수는 아니지만 이 방법을 기본으로 해서 다양하게 응용

테스트 케이스를 위한 설정
테스트는 케이스 격리된 환경에서 실행하고, 끝나면 데이터를 초기화하는 것이 좋다. 그런 면에서 메모리 DB를 사용하는 것이 가장 이상적이다. 추가로 테스트 케이스를 위한 스프링 환경과, 일반적으로 애플리케이션을 실행하는 환경은 보통 다르므로 설정 파일을 다르게 사용하자. 다음과 같이 간단하게 테스트용 설정 파일을 추가하면 된다.

  • test/resource에 application.yml 파일이 있으면 테스트에서 스프링을 실행하면 여기에 있는 파일을 읽고 없으면 src/resource에 있는 yml 파일을 읽는다.

JUnit과 Assertions

📜자료

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%8D%B0%EC%9D%B4%ED%84%B0-jpa

profile
기록기록기록기록기록

0개의 댓글