실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
스프링 핵심 원리 - 기본편 - 인프런
스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다. 초급 프레임워크 및 라이브러리 웹 개발 서버 개발 Back-End Spring 객체지향 온
www.inflearn.com
강의를 들으며 생각 정리 + "자바 ORM 표준 JPA 프로그래밍" 책 참고
회원 리포지토리 개발
@Repository
@RequiredArgsConstructor
public class MemberRepository {
private final EntityManager em;
public void save(Member member) {
em.persist(member);
}
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로 주입해야한다.
@PersistenceContext
private EntityManager em;
그러나 스프링부트가 엔티티 매니저에도 @Autowired를 지원한다.
@Autowired
private EntityManager em;
따라서, final 키워드와 함께 롬복의 애노테이션인 @RequiredArgsConstructor를 사용해서 생성자 주입이 가능하다.
+) @Autowired를 사용할 때, 필드 주입은 필드를 수정하기 어렵고, 수정자 주입은 수정자를 통해 필드가 불시에 바뀔 우위험이 있다. 그래서 한번 엔티티를 생성할 때만 필드를 등록할 수 있는 생성자 주입을 많이 사용한다.
-> @RequiredArgsConstructor : final 키워드가 있는 필드들을 모아 생성자를 생성해준다.
회원 서비스 개발
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
/**
* 회원 가입
*/
@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()) {
throw new IllegalStateException("이미 존재하는 회원입니다.");
}
}
//회원 전체 조회
public List<Member> findMembers() {
return memberRepository.findAll();
}
public Member findOne(Long memberId) {
return memberRepository.findOne(memberId);
}
}
모든 데이터 변경은 트랜잭션 안에서 수행되어야 하기 때문에 일반적으로 직접 비즈니스 로직을 사용하는 서비스에 @Transactional 애노테이션을 걸어준다. (각 메소드 결과가 성공하면 commit을 하고, 런타임 예외가 터지면 rollback을 한다)
또한, 트랜잭션에서 readOnly=true 옵션을 사용하면 영속성 컨텍스트를 flush 하지 않아서 약간의 성능 향상이 있다. 읽기 전용 메소드에서 사용하면 좋다.
-> @Transactional(readOnly=true)를 글로벌하게 사용하고 데이터를 write하는 메소드에는 따로 @Transactional(readOnly=false)를 붙여준다.(readOnly의 default가 false이기 때문에 따로 옵션을 붙이지 않아도 좋다)
+) 회원과 리포지토리를 왜 나누어야 할까?
-> 서비스 계층은 비즈니스 로직이 있는 곳으로, 리포지토리는 엔티티를 관리하고 저장하는 곳으로 역할이 나누어져 있다. 단일 책임 원칙(SRP)를 지키는 것이다. 또한, 여러 서비스에서 한 리포지토리를 참조할 수 있다.
회원 기능 테스트
테스트를 포함한 전체 코드는 깃허브에 등록했다.
테스트 케이스를 위한 설정
- @Transactional을 테스트에서 사용하면 테스트가 끝날 때 자동으로 DB를 롤백해준다. 그래서 persist한 엔티티가 flush되지 않는다.
- 테스트는 케이스 격리된 환경에서 실행하고, 끝나면 데이터를 초기화하는 것이 좋다. 그런 면에서 메모리 DB를 사용하는 것이 가장 이상적이다. 그래서 실제 main 코드와 다르게 테스트 전용 설정 파일(application.yml)을 사용하는 것이 좋다.
만약 test/resources/ 위치에 설정파일이 있으면 테스트에서 스프링 실행시 해당 위치를 우선적으로 읽는다.
+) 테스트 케이스에도 application.yml을 생성하면 스프링부트는 datasource 설정이 없어도, 기본적을 메모리 DB를 사용하고, driver-class도 현재 등록된 라이브러를 보고 찾아준다. 여기서는 h2 라이브러리를 사용하기 때문에 자동적으로 h2 메모리 DB를 사용하게 된다. 추가로 ddl-auto 도 create-drop 모드로 기본 동작한다. 따라서 데이터소스나, JPA 관련된 별도의 추가 설정을 하지 않아도 된다.
+) 2021.05.15 추가 : mysql은 따로 테스트용 메모리 DB를 만들어주지 않는다. h2 라이브러리가 포함되어 있을 때만 테스트 실행 가능했다.
'java > jpa' 카테고리의 다른 글
[JPA] 주문 도메인 개발 (0) | 2021.02.14 |
---|---|
[JPA] 상품 도메인 개발 (0) | 2021.02.13 |
[JPA] 애플리케이션 구현 준비 (0) | 2021.02.11 |
[JPA] 도메인 분석 설계 (0) | 2021.02.11 |
[JPA] 프로젝트 환경설정 (0) | 2021.02.10 |