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]': trace
728x90
반응형
'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 |
댓글