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 |
댓글