728x90
    
    
  반응형
    
    
    
  회원 기능인 회원 등록, 조회 기능을 구현해보자
회원 레포지토리 개발
파일 경로
main/java/jpabook/jpashop/repository/MemberRepository.java
소스 구현
package jpabook.jpashop.repository;
@Repository
public class MemberRepository {
    
    @PersistenceContext
    private EntityManager em;
    /**
     * save Member
     * @param member
     */
    public void save(Member member) {
        em.persist(member);
    }
    /**
     * find one Member
     * @param id
     * @return Member
     */
    public Member findOne(Long id) {
        return em.find(Member.class, id);
    }
    /**
     * find all Member
     * @return List<Member>
     */
    public List<Member> findAll() {
        // JPQL은 엔티티를 대상으로 쿼리, SQL은 테이블을 대상으로 쿼리
        return em.createQuery("select m from Member m", Member.class)
                .getResultList();
    }
    /**
     * find Member by name
     * @param name
     * @return List<Member>
     */
    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
 - Spring Boot Application 실행 시 Component 스캔을 하여 스프링 Bean으로 등록하는데- @Repository는- @Component의 하위기 때문에 스프링 Bean으로 자동 등록된다.
 - JPA 예외를 스프링 기반 예외로 예외 변환
- @PersistenceContext
 - 스프링이 생성한 JPA의 EntityManager를 주입하는 어노테이션
 -- @PersistenceUnit은 EntityManagerFactory를 주입하는 어노테이션
회원 서비스 개발
파일 경로
main/java/jpabook/jpashop/service/MemberService.java
소스 구현
package jpabook.jpashop.service;
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class MemberService {
    private final MemberRepository memberRepository;
    /**
     * join Member
     * @param member
     * @return Long
     */
    @Transactional  // 클래스에 선언 후 메소드에 선언 시 메소드 선언에 우선권
    public Long join(Member member) {
        // 회원 중복 확인
        validateDuplicateMember(member);
        memberRepository.save(member);
        // 아직 DB에 저장되기 전임에도 영속성 컨텍스트에 해당 key값으로 저장되어 있기 때문에 그 key를 PK 필드에 셋팅해준다.
        // 따라서 엔티티 PK필드를 꺼낼때 항상 값이 있다는 것이 보장된다.
        return member.getId();
    }
    /**
     * check duplicate Member
     * @param member
     */
    private void validateDuplicateMember(Member member) {
        List<Member> findMembers = memberRepository.findByName(member.getName());
        // 회원 조회결과가 있다면 중복
        // 만약의 경우 여러곳에서 동시에 같은 이름으로 가입 시 중복 체크가 안될 수 있다.
        // 이러한 경우를 방지하기 위해 name에 unique 제약조건을 설정하는 것을 권장한다.
        if(!findMembers.isEmpty()) {
            throw new IllegalStateException("이미 존재하는 회원입니다.");
        }
    }
    /**
     * find all Member
     * @return List<Member>
     */
    public List<Member> findMembers() {
        return memberRepository.findAll();
    }
    /**
     * find one Member
     * @param memberId
     * @return
     */
    public Member findOne(Long memberId) {
        return memberRepository.findOne(memberId);
    }
}- @Service
 - 비즈니스 로직을 수행하는 클래스임을 나타내는 어노테이션
- @Transactional
 - 트랜잭션을 처리, 관리해주는 어노테이션, JPA의 모든 데이터 변경관련 로직은 트랜잭션 내에서 실행되어야 한다.
 -- javax와- springframework가 각각 제공하는데 이미 스프링 관련 의존성을 사용한 로직이 많기 때문에- springframework가 제공하는 것을 사용하면 더 많은 옵션을 사용할 수 있다.
 -- readOnly=true로 설정하면 데이터를 조회만 하기 때문에 영속성 컨텍스트를- flush하지 않아 약간의 성능이 향상된다. 하지만 데이터 변경은 반영되지 않는다.
- @Autowird
 - 스프링 Bean을 찾아 주입해주는 어노테이션
- @RequiredArgsConstructor
 - Lombok에서 제공하는 어노테이션으로- final키워드가 선언된 변수로 생성자를 만든다.
 -- @AllArgsConstructor는 선언된 모든 변수로 생성자를 만든다.
다양한 Injection
Field Injection
@Autowired
private MemberRepository memberRepository;- 필드를 선언하여 주입하는 방법
- 단점 : 변경이 불가능하다.
Setter Injection
private MemberRepository memberRepository;
@Autowired
public void setMemberRepository(MemberRepository memberRepository) {
    this.memberRepository = memberRepository;
}- Setter를 생성하여 주입하는 방법
- 장점 : 테스트 코드 작성 시 mock 객체를 주입하여 사용할 수 있다.
- 단점 : 애플리케이션 실행 시 임의로 변경이 가능하여 위험성이 존재하고 애플리케이션 동작 중 변경 가능성이 적다.
Constructor Injection
private MemberRepository memberRepository;
@Autowired
public MemberService(MemberRepository memberRepository) {
   this.memberRepository = memberRepository;
}- 생성자를 사용하여 주입하는 방법
- 장점
 - 임의로 변경 가능성 없다.
 - 생성자 사용 주입 시 주입 할 변수에final키워드를 추가하면 컴파일 시점에 해당 변수 미사용 오류를 체크할 수 있다.
- 생성자가 하나면 @Autowired를 생략할 수 있다.
Lombok
@Repository
@RequiredArgsConstructor
public class MemberRepository {
    private final EntityManager em;
}- 스프링 데이터 JPA를 사용하면 @PersistenceContext를@Autowired로 변경하여 사용할 수 있도록 제공한다.
회원 기능 테스트
테스트 요구사항
- 회원가입을 성공해야 한다.
- 회원가입 시 중복된 이름이 있으면 예외가 발생해야 한다.
테스트 구조
given ~ when ~ then 방식으로 '어떤 것이 주어졌을때(=given) 이렇게 하면(=when) 이렇게 된다.(=then)' 라는 의미다.
테스트 시 특이사항
테스트 진행 시 자동으로 롤백되기 때문에 진행 로그에서 insert SQL이 보이지 않는다. insert SQL을 확인하고 싶으면 메소드에 @Rollback(value = false)를 선언하여 롤백을 진행하지 않거나 EntityManager를 주입받아 EntityManager.flush()를 호출하여 영속성 컨텍스트를 비워 표시할 수 있다. 이때 트랜잭션 롤백은 false로 지정하지 않으면 발생한다.
테스트 파일 경로
test/java/jpabook/jpashop/service/MemberServiceTest.java
테스트 구현
package jpabook.jpashop.service;
@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class MemberServiceTest {
    @Autowired
    MemberService memberService;
    @Autowired
    MemberRepository memberRepository;
    // @Autowired
    // EntityManager em;
    
    @Test
    // @Rollback(value = false)
    public void 회원가입() throws Exception {
        // given
        Member member = new Member();
        member.setName("kim");
        // when
        Long savedId = memberService.join(member);
        // then
        // em.flush();
        assertEquals(member, memberRepository.findOne(savedId));
    }
    @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("예외가 발생해야 한다.");
    }
}- @RunWith
 - JUnit 실행 시 스프링과 엮어서 실행
- @SpringBootTest
 - 스프링 부트 컨테이너에서 테스트 실행, 해당 어노테이션 없을 경우- @Autowired실패
- @Transactional
 - 테스트를 실행할 때마다 트랜잭션을 시작하고 테스트가 끝나면 트랜잭션을 강제로 롤백한다. (테스트 케이스에서 사용시)
테스트 결과
회원가입 테스트

중복회원 예외 테스트

중복회원 예외 테스트 실패

중복회원 예외 테스트 내 fail()

테스트 케이스를 위한 메모리 데이터베이스 설정
- 테스트 케이스는 격리된 환경에서 실행하고 끝나면 데이터를 초기화하는 것이 좋다. 그런면에서 메모리 DB를 사용하는 것이 가장 이상적이다.
- 테스트 케이스를 위한 환경과 일반 애플리케이션 실행 환경은 보통 다르므로 설정파일을 각각 다르게 사용한다.
 - 일반 애플리케이션 설정 파일 경로 :main/resources/application.yml
 - 테스트 케이스 설정 파일 경로 :test/resources/application.yml
- 스프링 부트는 기본적으로 메모리DB를 사용하고 driver-class도 현재 라이브러리를 보고 찾아준다. 또한ddl-auto옵션도create-drop모드로 동작한다.
# 테스트용 설정 파일
# spring:
  # datasource:
  #   url: jdbc:h2:tcp://localhost/~/jpashop
  #   username: sa
  #   password:
  #   driver-class-name: org.h2.Driver
  # jpa:
  #   hibernate:
  #     ddl-auto: create
  #   properties:
  #     hibernate:
  #       # '[show_sql]': auto
  #       '[format_sql]': true
logging:
  level:
    '[org.hibernate.SQL]': debug
    '[org.hibernate.type]': trace728x90
    
    
  반응형
    
    
    
  'Dev > JPA' 카테고리의 다른 글
| [JPA] JPA 활용 I - 주문 도메인 개발 (0) | 2021.09.23 | 
|---|---|
| [JPA] JPA 활용 I - 상품 도메인 개발 (0) | 2021.09.17 | 
| [JPA] JPA 활용 I - 애플리케이션 구현 준비 (0) | 2021.09.16 | 
| [JPA] JPA 활용 I - 도메인 분석 설계 (0) | 2021.09.15 | 
| [JPA] JPA 활용 I - 프로젝트 환경설정 (0) | 2021.09.14 | 
댓글