mojo's Blog
회원 도메인 개발 본문
회원 리포지토리 개발
구현 기능
- 회원 등록
- 회원 목록 조회
순서
- 회원 리포지토리 개발
- 회원 서비스 개발
- 회원 기능 테스트
@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() {
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
public class MemberService {
@Autowired
private 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("이미 존재하는 회원입니다.");
}
}
/**
* 회원 전체 조회
*/
@Transactional(readOnly = true)
public List<Member> findMembers() {
return memberRepository.findAll();
}
@Transactional(readOnly = true)
public Member findOne(Long memberId) {
return memberRepository.findOne(memberId);
}
}
- @Transactional(readOnly = true) : 데이터를 조회할 때는 readOnly 를 true 로 지정하여 읽기에 대한
성능을 최적화하여 더 빠르게 읽을 수 있도록 설정할 수 있다. (기본값은 false)
- @Autowired : memberRepository 에 대한 스프링 빈을 의존관계 주입을 자동으로 해준다.
위 코드는 메서드에 직접 트랜잭션 애너테이션을 부여하였다.
그러나 클래스에 직접 애너테이션을 부여하여 설정할 수 있다.
@Service
@Transactional(readOnly = true)
public class MemberService {
@Autowired
private 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();
}
public Member findOne(Long memberId) {
return memberRepository.findOne(memberId);
}
}
join 메서드에는 조회가 아닌 삽입이 이뤄지기 때문에, @Transactional 을 붙여서 readOnly 의 값이
false가 될 수 있도록 설정하였다.
private final MemberRepository memberRepository;
@Autowired
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
위와 같이 생성자 주입을 하여 테스트 코드를 작성할 때 명시적으로 memberRepository 를 주입해줘야
한다는 것을 알 수 있다.
@RequiredArgsConstructor
public class MemberService {
private MemberRepository memberRepository;
사실 의존관계를 주입할 때 가장 좋은 방법은 롬북을 활용하여 @RequiredArgsConstructor 를 붙이면 된다.
알아서 MemberService 에서 memberRepository 에 필요한 의존관계를 주입해줄 것 이다.
회원 기능 테스트
※ 테스트 요구사항
회원가입을 성공해야 한다.회원가입 할 때 같은 이름이 있으면 예외가 발생해야 한다.
[cmd + shift + t] 단축키를 누르면 테스트 코드를 알아서 작성해 줄 수 있다.
위와 같이 Run tests using 을 IntelliJ IDEA 로 설정해야 정상적으로 수행이 된다.
@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class MemberServiceTest {
@Autowired
private MemberService memberService;
@Autowired
private MemberRepository memberRepository;
@Test
public void 회원가입() throws Exception {
// given
Member member = new Member();
member.setName("mojo");
// when
Long savedId = memberService.join(member);
// then
assertEquals(member, memberRepository.findOne(savedId));
}
...
}
정상적으로 수행되었다.
이번엔 중복 회원 가입에 대한 테스트 코드를 작성해보도록 하자.
@Test
public void 중복_회원_가입() throws Exception {
// given
Member member1 = new Member();
member1.setName("mojo");
Member member2 = new Member();
member2.setName("mojo");
// when
memberService.join(member1);
try {
memberService.join(member2);
} catch (IllegalStateException e){
return;
}
// then
fail("예외가 발생해야 한다!");
}
여기서 try - catch 문을 활용하여 예외를 확인한다.
그러나 위 코드를 다음과 같이 간결하게 작성할 수 있다.
@Test(expected = IllegalStateException.class)
public void 중복_회원_가입() throws Exception {
// given
Member member1 = new Member();
member1.setName("mojo");
Member member2 = new Member();
member2.setName("mojo");
// when
memberService.join(member1);
memberService.join(member2);
// then
fail("예외가 발생해야 한다!");
}
테스트를 할 때 데이터베이스에 직접 데이터를 넣고 초기화하는 작업을 수행하는 것은 비효율적이다.
따라서 이를 해결하기 위해 메모리 공간 내에서 해결할 수 있도록 하는 방법이 존재한다.
우선 test 패키지 아래에 resources 를 생성하고 application.yml 을 동일하게 넣어준다.
그 다음에 datasource 의 url 을 다음과 같이 변경해준다.
spring:
datasource:
url: jdbc:h2:mem:test
username: sa
password:
driver-class-name: org.h2.Driver
jpa:
hibernate:
ddl-auto: create
properties:
hibernate:
# show_sql: true
format_sql: true
logging:
level:
org.hibernate.SQL: debug
org.hibernate.type: trace
위와 같이 세팅이 끝나면 db 띄우지 않고 수행이 된다.
그러나 더 좋은 방법은 spring 의 datasource 및 jpa 를 주석처리 하는 방법이다.
spring:
# datasource:
# url: jdbc:h2:mem:test
# username: sa
# password:
# driver-class-name: org.h2.Driver
# jpa:
# hibernate:
# ddl-auto: create
# properties:
# hibernate:
# show_sql: true
# format_sql: true
logging:
level:
org.hibernate.SQL: debug
org.hibernate.type: trace