회원 리포지토리 개발
@Repository
public class MemberRepository {
@PersistenceContext
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() { //여러건 검색은 em.createQuery로 JPQL 작성
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 애노테이션을 붙임으로써 컴포넌트 스캔 대상이 되어서 스프링 빈에 등록이 된다.
- @PersistenceContext: 엔티티 매니저를 스프링 빈으로 주입
회원 서비스 개발
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
// public MemberService(MemberRepository memberRepository) {
// this.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)를 해두면 jpa가 최적화한다. 그래서 읽기가 더 많아서 큰곳에 readOnly = true로 설정하고, 쓰기에 따로 @Transactional을 붙인 것이다.
- 멀티 쓰레드 환경에서 같은 이름으로 동시에 가입하려는 사람이 있어서 둘다 검증을 통과할 수 있으므로, 데이터베이스에서 unique 제약조건을 걸어두면 좋다.
- MemberRepository 주입은 필드 주입보다, 또 setter 주입보다, 생성자 주입으로 하는 것이 더 좋다.
- 생성자가 하나면, @Autowired를 생략할 수 있다.
- 생성자 주입으로는 변경 불가능한 안전한 객체 생성이 가능하다.
- 실행 중에 변경할 일이 없으므로 final로 하는 것이 좋다.
- @RequiredArgsConstructor를 사용하면 final이 붙은 것은 자동으로 생성자를 만들어준다.
- 그래서 주석으로 작성한 부분인 생성자가 필요없게 된다.
또한 앞에서 작성한 MemberRepository도 생성자 주입을 할 수 있다. 스프링 데이터 JPA를 사용하면 @PersistenceContext를 @Autowired로 쓸 수 있어서 MemberService에 적용한 것과 같다.
@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() { //여러건 검색은 em.createQuery로 JPQL 작성
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();
}
}
그래서 이렇게 @RequiredArgsContructor를 사용하면 생성자 주입 간단하게 작성할 수 있게 된다.
회원 기능 테스트
- 회원가입을 성공해야 한다.
- 회원가입을 할 때 같은 이름이 있으면 예외가 발생한다.
@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
class MemberServiceTest {
@Autowired MemberService memberService;
@Autowired MemberRepository memberRepository;
@Autowired EntityManager em;
@Test
public void 회원가입() throws Exception {
//given
Member member = new Member();
member.setName("memberA");
//when
Long savedId = memberService.join(member);
//then
Assert.assertEquals(member, memberRepository.findOne(savedId));
}
@Test
public void 중복_회원_예외() throws Exception {
//given
Member member1 = new Member();
member1.setName("kim");
Member member2 = new Member();
member2.setName("kim");
//when
memberService.join(member1);
Assertions.assertThrows(IllegalStateException.class, ()-> {memberService.join(member2);});
}
}
- @RunWith(SpringRunner.class) : 스프링과 테스트 통합
- @SpringBootTest : 스프링 부트 띄우고 테스트(이게 없으면 @Autowired 다 실패)
- @Transactional : 반복 가능한 테스트 지원, 각각의 테스트를 실행할 때마다 트랜잭션을 시작하고 테스트가
끝나면 트랜잭션을 강제로 롤백
728x90
'Spring' 카테고리의 다른 글
[Springboot & JPA 1] 주문 도메인 개발 (0) | 2024.05.04 |
---|---|
[Springboot & JPA 1] 상품 도메인 개발 (0) | 2024.05.03 |
[Spring boot& JPA 1] 도메인 분석 설계 (3) | 2024.04.28 |
[Spring DB2] 스프링 트랜잭션 전파2 - 활용 (0) | 2024.03.31 |
[Spring DB2] 스프링 트랜잭션 전파1 - 기본 (0) | 2024.03.31 |