강의 : Inflearn-실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
기간 : 7/28~8/3
예제를 단순화 하기 위해 다음 기능은 구현X
로그인과 권한 관리X
파라미터 검증과 예외 처리X
상품은 도서만 사용
카테고리는 사용X
배송 정보는 사용X
구현 기능
순서
@Repository // spring bean 으로 등록
public class MemberRepository {
@PersistenceContext // 엔티티 메니저( EntityManager ) 주입
private 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(){ // 전체 조회
// sql은 테이블을 대상으로 하지만 JPQL은 class를 대상으로 함
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();
}
}
기술 설명
@Service
@Transactional(readOnly = true) // 읽기 전용으로 선언함으로써 성능 최적화, 데이터 변경 안됨
//@AllArgsConstructor -> 생성자 injection 만들어주는 효과
@RequiredArgsConstructor // final 변수만 매개변수로하는 생성자 생성
public class MemberService {
// field 방식 injection은 코드를 변경하기 어려움
private final MemberRepository memberRepository;
/*@Autowired // 생성자 injection 이 요즘 권장되는 방식임 + 생성자가 딱 하나만 있으면 @Autowired 없어도 빈으로 등록됨
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}*/
/**
* 회원가입
*/
@Transactional // 따로 설정한 것은 class 트랜젝션보다 우선권을 갖음
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();
}
public Member findOne(Long memberId){
return memberRepository.findOne(memberId);
}
}
기술 요약
참고: 실무에서는 검증 로직이 있어도 멀티 쓰레드 상황을 고려해서 회원 테이블의 회원명 컬럼에 유니크 제약 조건을 추가하는 것이 안전하다.
참고: 스프링 필드 주입 대신에 생성자 주입을 사용하자
필드주입
public class MemberService {
@Autowired
MemberRepository memberRepository;
}
생성자 주입
public class MemberService {
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
...
}
테스트 요구사항
1. 회원가입을 성공해야 한다.
2. 회원가입 할 때 같은 이름이 있으면 예외가 발생해야 한다.
@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
class MemberServiceTest {
@Autowired MemberService memberService;
@Autowired MemberRepository memberRepository;
@Autowired EntityManager em;
@Test
@Rollback(false) // Transactional 이 Rollback 시켜버리는 것을 막음
public void test_회원가입() throws Exception{
//given : ~가 주어졌을 때
Member member = new Member();
member.setName("ryu");
//when : 이러쿵 저러쿵 하면
Long saveId = memberService.join(member);
//then : 결과가 이렇다~
em.flush(); // 영속성 컨텍스트에 있는 내용을 DB에 반영을 시킴
assertEquals(member, memberRepository.findOne(saveId));
}
@Test
public void test_중복회원예외() throws Exception{
//given
Member member1 = new Member();
member1.setName("ryu1");
Member member2 = new Member();
member2.setName("ryu1");
//when
memberService.join(member1);
//then
IllegalStateException thrown = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
assertEquals("이미 존재하는 회원입니다.", thrown.getMessage());
// fail("예외가 발생해야 한다."); // 코드가 여기까지 오면 테스트 코드를 잘못 작성한 것이다!
}
}
기술 설명
참고 : 스프링 부트는 datasource 설정이 없으면, 기본적을 메모리 DB를 사용하고, driver-class도 현재 등록된 라이브러를 보고 찾아준다. 추가로 ddl-auto 도 create-drop 모드로 동작한다. 따라서 데이터소스나, JPA 관련된 별도의 추가 설정을 하지 않아도 된다.