SpringBoot와 JPA, Thymeleaf를 사용하여 간단한 쇼핑몰을 구현하려 합니다. 프로젝트 생성이나 Gradle 빌드는 깃허브 BincoShop을 참고해주세요! 포스팅은 도메인&테이블 설계 -> 엔티티 개발 -> 회원 서비스 -> 상품 서비스 -> 주문 서비스 순으로 진행됩니다. 포스팅의 잘못된 부분은 언제든 댓글로 남겨주시면 수정하겠습니다😀
개요
지난 포스팅까지는 엔티티 개발을 진행하였습니다. 오늘부터는 비즈니스 로직 개발을 진행하는데, 회원 서비스 먼저 개발을 진행하려 합니다. 포스팅의 모든 내용은 김영한 강사님의 강의를 참고했습니다😄
Repository📘
@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();
}
}
가장 먼저 Repository를 생성합니다. @Repository 어노테이션을 이용하여 스프링 빈으로 등록하고 JPA 예외를 스프링 기반 예외로 변환시켜줍니다.
@RequiredArgsConstructor 어노테이션은 final로 선언된 필드를 생성자를 만들어주는 롬복 어노테이션입니다. JPA를 사용하기 때문에 EntityManager를 주입받아야 하므로 final로 EntityManger를 선언하고 @RequiredArgsConstructor로 생성자를 주입했다고 생각하시면 됩니다.
쇼핑몰에서 사용할 회원 서비스 기능은 회원 단건 조회, 전체 조회, 회원 등록, 이름 검색 기능이 있습니다.
- save(Member member) : Controller로부터 넘겨받은 Member를 persist하여 저장해주는 비즈니스 로직
- findOne(Long id) : Controller로부터 넘겨받은 Member id를 이용하여 find() 메소드로 멤버를 찾는 비즈니스 로직
- findAll() : JPQL을 사용하여 모든 멤버를 찾아 List로 반환하는 쿼리를 작성
- findByName(String name) : Controller로부터 넘겨받은 이름값을 이용하여 JPQL 쿼리를 작성. :name은 파라미터 값을 바인딩시키는 역할이고, setParameter는 파라미터를 세팅하는 역할
Service📙
@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) {
// Exception
List<Member> findMembers = memberRepository.findByName(member.getName());
if (!findMembers.isEmpty()) {
throw new IllegalStateException("이미 존재하는 회원입니다.");
}
}
// 회원 전체 조회
public List<Member> findMembers() {
return memberRepository.findAll();
}
// 회원 1건 조회
public Member findOne(Long id) {
return memberRepository.findOne(id);
}
}
서비스 클래스이기 때문에 @Service 어노테이션을 기입합니다. 두번째로 데이터 변경에 있어서 꼭 필요한 @Transaction 어노테이션을 작성하는데, 여기서 유의할 점은 readOnly=true로 작성되어 있는 점입니다.
데이터의 변경이 없는 읽기 전용 메서드에는 readOnly를 true로 설정하는 것이 좋습니다. 영송석 컨텍스를 플러시 하지 않으므로 성능 향상이 되기 때문입니다. 하지만 조회할 때만 사용해야 하기 때문에, 데이터를 저장하는 save() 메서드에는 따로 @Transaction을 기입해줘야 합니다.
@Repository에서 사용했던 것처럼 final로 필드를 생성하고 @RequiredArgsConstructor 어노테이션을 이용하여 생성자를 주입해줍니다.(MemberRepository)
회원가입을 진행하는 join 메소드는 로직 진행 전에 중복된 맴버가 있는지 체크해야 합니다. validateDuplicateMember 메소드를 보면 매개변수로 Member를 받아서 해당 멤버의 이름을 통해 현재 DB에 같은 값이 있는지 체크합니다.
하지만 실무에서는 멀티쓰레드 환경이므로 동시다발적으로 회원 가입을 진행할 수 있기 때문에, DB의 회원명 컬럼에 유니크 제약 조건을 추가하는 것이 보다 안전합니다.
📌동명이인 가능성은 배제하였습니다.
findMembers(), findOne() 메서드는 단순 로직이므로 설명은 생략 하겠습니다.
TDD📔
테스트 환경설정은 따로 포스팅하지 않겠습니다. 혹여 따라 하시는 분들은 H2 데이터베이스 연결과 깃허브 소스 application.yml 파일을 확인해서 환경설정을 진행해주세요😄
@SpringBootTest
@Transactional
class MemberServiceTest {
@Autowired
MemberService memberService;
@Autowired
MemberRepository memberRepository;
@Autowired
EntityManager em;
@Test
@DisplayName("회원가입 테스트")
@Rollback(false) // DB로 직접 보고 싶으면 추가
public void joinTest() throws Exception {
// given
Member member = new Member();
member.setName("kim");
// when
Long savedId = memberService.join(member);
// then
em.flush();
assertEquals(member, memberRepository.findOne(savedId));
}
}
@SpringBootTest를 기입하지 않으면 @Autowired 자체가 되지 않기 때문에 TDD를 진행하실 때 꼭 기입하셔야 합니다. 또한 @Transactional 어노테이션은 반복 가능한 테스트를 지원하고 각각의 테스트를 실행할 때마다 트랜잭션을 시작하고 테스트가 끝나면 트랜잭션을 강제로 롤백시킵니다(테스트 케이스에서만)
회원가입 테스트는 테스트용 member 객체를 생성해서 이전에 만든 memberService의 join 메서드를 이용하여 회원가입을 진행합니다. then 검증 단계에서는 em.flush로 영속성 컨텍스트에 주입하고 assertEquals로 테스트로 가입한 멤버와 findOne 메서드로 찾은 멤버가 같은지 검증합니다.
📌@Transactional로 인해 동작하기 때문에 영속성 컨텍스트상 같은 멤버로 간주합니다.
@Test
@DisplayName("회원가입 중복 예외")
public void duplicateTest() throws Exception {
// given
Member member1 = new Member();
member1.setName("choi");
Member member2 = new Member();
member2.setName("choi");
// when
memberService.join(member1);
try {
memberService.join(member2);
} catch (IllegalStateException e) {
return;
}
// then
fail("예외가 발생해야 한다.");
}
회원가입 중복 테스트는 테스트 맴버 2개를 작성하고 순서대로 join 메서드를 통해 회원가입을 진행합니다. 이때 이름이 같을 때는 이전에 만든 Service 단의 validateDuplicateMember를 타는지 검증합니다.
👨👩👦👦 오픈채팅방 운영
취업을 준비하는 예비 개발자분들을 위한 질문&답변할 수 있는 공간을 만들었습니다. 취업과 이직을 하기 위해서 어떤 걸 중점적으로 준비해야 하는지부터 포트폴리오&이력서 작성법 등 다양한 질문들을 받고 답변을 드립니다. 참여하셔서 다양한 정보 얻고 가시면 좋을 것 같네요😁
참여코드 : 456456
https://open.kakao.com/o/gVHZP8dg
👨💻 전자책 출간
아울러 제가 🌟비전공자에서 2년만에 보안 전문 중견기업으로 이직 한 방법들을 정리한 전자책을 출간 하게 되었습니다. 어떤 걸 공부해야 하는지, 이직을 위해서 무엇을 준비해야 하는지, 제가 받았던 기술 면접 리스트 등 다양한 목차로 구성되어 있습니다. 또한, 구매 시 1:1 채팅을 이용하여 포트폴리오 첨삭을 도와드리고 있습니다. 🐕전자책으로 얻은 모든 수익은 유기견 센터 '팅*벨 입양센터'에 후원될 예정입니다. 관심 있으신 분들은 아래 링크를 참고해주세요😁
마치며
지금까지 회원 서비스를 만들었습니다. 다음 포스팅은 상품 서비스 개발을 진행하겠습니다😀
관련 포스팅
Reference
'JPA' 카테고리의 다른 글
SpringBoot JPA 쇼핑몰 주문 서비스 I (0) | 2022.09.15 |
---|---|
SpringBoot JPA 쇼핑몰 상품 서비스 개발 (0) | 2022.09.14 |
SpringBoot JPA 쇼핑몰 엔티티 개발Ⅱ (0) | 2022.09.14 |
SpringBoot JPA 쇼핑몰 엔티티 개발Ⅰ (0) | 2022.09.13 |
SpringBoot JPA 쇼핑몰 도메인&테이블 설계 (0) | 2022.09.07 |