728x90
애플리케이션 아키텍처
1. 계층형 구조 사용
- controller, web : 웹 계층
- service : 비즈니스 로직, 트랜잭션 처리
- repository : JPA를 직접 사용하는 계층, 엔티티 매니저 사용
- domain : 엔티티가 모여있는 계층, 모든 계층에서 사용
회원 리포지토리 개발
package bookbook.shopshop.repository;
import bookbook.shopshop.domain.Member;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import javax.persistence.EntityManager;
import java.util.List;
@Repository
@RequiredArgsConstructor
public class MemberRepository {
// @PersistenceContext // 스프링 부틍 사용시 생략 가능
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();
}
}
- 스프링 데이터 JPA를 사용하면 @Autowired가 @PersistenceContext를 생략해도 엔티티 매니저를 자동 주입 해준다.
- @Repository : 스프링 빈으로 등록, JPA 예외를 스프링 기반 예외로 예외 변환
- @PersistenceContext : 엔티티 매니저(EntityManager) 주입
- @PersistenceUnit : 엔티티 매니저 팩토리(EntityManagerFactory) 주입
회원 서비스 개발
package bookbook.shopshop.service;
import bookbook.shopshop.domain.Member;
import bookbook.shopshop.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@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);
}
}
- @Service : 스프링 빈 등록
- @Transactional : 트랜잭션, 영속성 컨텍스트
- readOnly = true : 데이터의 변경이 없는 읽기 전용 메서드에 사용
- 영속성 컨텍스트를 flush 하지 않으므로 약간의 성능이 향상된다.
- 데이터베이스 드라이버가 지원하면 DB 성능을 향상 시킬 수 있다.
- readOnly = true : 데이터의 변경이 없는 읽기 전용 메서드에 사용
- 실무에서 검증 로직이 있어도 멀티 쓰레드 상황을 고려해서 회원 테이블의 회뭔명 컬럼에 유니크 제약 조건을 추가하는 것이 안전하다.
- 예) 회원 중복 검사
참고로 insert 메서드(등록)는 엔티티의 pk 값을 반환하고, update는 아무것도 반환하지 않고, 조회는 내부의 변경이 없는 메서드로 설계하면 좋다.
메서드를 호출했을 때 내부에서 변경(사이드 이펙트)가 일어나는 메서드인지, 아니면 내부에서 변경이 전혀 일어나지 않는 메서드인지 명확하게 분리해야 한다.
그러기 위해선 커맨드 객체를 반환하지 않는 것이 좋다.
회원 기능 테스트
package bookbook.shopshop.service;
import bookbook.shopshop.domain.Member;
import bookbook.shopshop.repository.MemberRepository;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import static org.junit.jupiter.api.Assertions.assertThrows;
@SpringBootTest
@Transactional
class MemberServiceTest {
@Autowired
MemberService memberService;
@Autowired
MemberRepository memberRepository;
@Autowired
EntityManager em;
@Test
@Rollback(false)
void 회원가입() {
// given
Member member = new Member();
member.setName("kim");
// when
Long savedId = memberService.join(member);
// then
em.flush();
Assertions.assertThat(member).isEqualTo(memberRepository.findOne(savedId));
}
@Test
void 중복_회원_검사() {
// given
Member member1 = new Member();
member1.setName("kim");
Member member2 = new Member();
member2.setName("kim");
// when
memberService.join(member1);
// then
assertThrows(IllegalStateException.class,
() -> memberService.join(member2));
}
}
1. 메모리 DB에서 테스트 하는 방법
테스트를 할 때 was를 띄울 떄 메모리 db 도 같이 띄어서 테스트를 진행하는 방법이 좋은 방법이다.
테스트 폴더에 application.yml 처럼 설정 파일을 만들면 해당 파일이 메인 폴더의 설정 폴더보다 우선권을 가진다.
📌 테스트 서버의 application.yml
spring:
datasource:
url: jdbc:h2:mem:test
username: sa
password:
driver-class-name: org.h2.Driver
output:
ansi:
enabled: always
jpa:
hibernate:
ddl-auto: create
properties:
hibernate:
format_sql: true
# --------------------------------
logging:
level:
org.hibernate.SQL: debug
org.hibernate.type: trace
'#----------' 기준으로 위의 부분은 다 생략해도 스프링 부트가 자동으로 메모리 DB로 연결해준다.
🔍 H2 메모리 db url 얻는 방법
H2 홈 페이지를 들어가서 Cheat Sheet를 누른다.
In-Memory 부분에 jdbc:h2mem:test 가 메모리 db 주소다.
👀 참고 자료
https://www.inflearn.com/questions/27795
728x90
'[JPA] > 스프링 부트와 JPA 활용 1 - 웹 애플리케이션 개발' 카테고리의 다른 글
[JPA] 웹 계층 개발 (0) | 2022.04.05 |
---|---|
주문 도메인 개발 (0) | 2022.04.05 |
[JPA] 상품 도메인 개발 (0) | 2022.04.04 |
[JPA] 도메인 분석 설계 (0) | 2022.04.01 |