@Repository
public class MemberRepository {
@PersistenceContext
private EntityManager em;
public Long save(Member member){
em.persist(member);
return member.getId();
}
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();
}
}
@PersistenceContext
를 넣어주어, EntityManager를 지정해준다.
@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);
}
}
@Transactional
을 사용하여, 모든 메소드가 하나의 Transaction 에서 일어나도록 하기.
@Transactional 이 javax
하고 spring
둘다 제공하는데, spring 패키지로 사용할 것을 권장! (기능이 더 많음)
readOnly
기능을 활용한다던지,하는 여러 기능들 포함!!
@Transaction
의 경우 기본적으로 readOnly=false
이므로, join
같은 메소드 위에 어노테이션을 달아주면 된다.
MemberRepository 를 불러옴에 있어,
@Autowired
를 사용하여 Spring Container bean 에서 지정할 수도 있지만,
- 그렇게 하면, memberRepository injection 을 수정할 수가 없음
- 그래서, setMemberRepository 를 만들어, 주입을 하는 방법으로
- Test code 를 작성할때, mock을 직접 주입 가능.
// bean 에서 field 주입
@Autowired
private MemberRepository memberRepository;
// method 로 주입
private MemberRepository memberRepository;
@Autowired
public void setMemberRepository(MemberRepository memberRepository){
this.memberRepository = memberRepository;
}
그런데 사실상, run time 에 memberRepository 를 바꿀 일이 없음. 맨 처음에 결정 되면, 그걸로 계속 씀
그래서!! 권장사항 -> Constructor 에서 주입하기!!
private final MemberRepository memberRepository;
@Autowired
public MemberService(MemberRepository memberRepository){
this.memberRepository = memberRepository;
}
!! 요즘 버전의 spring은 @Autowired
를 넣어주지 않아도, 자동으로 인식하여 injection을 해줌.
더 나아가서, @RequiredArgsConstructor
을 활용하면, final
인 field를 알아서 Constructor에 해당시켜줌!
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
// @Autowired
// public MemberService(MemberRepository memberRepository){
// this.memberRepository = memberRepository;
// }
...
}
@Repository
의 경우에도, EntityManager 에 final 로 설정해주어 Constructor에서 주입받을 수 있다!
(lombok @RequiredArgsConstructor)
@Repository
@RequiredArgsConstructor
public class MemberRepository {
private final EntityManager em;
...
}
@PersistenceContext 가 쓰여야 하지만, @Autowired 로 대체 될 수 있도록, 스프링 부트에서 지원
- @Autowired 가 생략 + @requiredArgsConstructor 로 final field 로 대체!
@Test
public void __() throws Exception {
//given
//when
//then
}
ctrl + shift + t
test code 생성해주는 단축키
tdd 자동완성
저번에 만들어두었던, tdd 로 쉽게 생성 가능! (Live Templates)
@RunWith(SpringRunner.class) // spring boot 연결
@SpringBootTest // spring boot 연결
@Transactional // 롤백 등 다양한 기능
public class MemberServiceTest {
@Autowired MemberService memberService;
@Autowired MemberRepository memberRepository;
@Test
public void 회원가입() throws Exception {
// given
Member member = new Member();
member.setName("kim");
// when
Long saveId = memberService.join(member);
// then
assertEquals(member, memberRepository.findOne(saveId));
}
...
}
위 코드가 실행되면, pass가 된다. (이것저것 sql 문을 날리면서)
하지만, DB 를 보면 Member Table은 비어있다. why?
Test 에서 사용되는 @Transactional
엔 기본적으로 rollback 기능이 있다.
그런데 코드를 보면, insert 쿼리가 나가는 것 조차 하지를 않음!
이 쿼리문을 확인해보고 싶은데 어떻게 해야하나?
따로, Entity Manager를 불러와서, flush를 진행해주면 된다!!
@Autowired EntityManager em;
...
// when
Long saveId = memberService.join(member);
em.flush();
...
따라서, 따로 @Rollback(false)
어노테이션을 달아주어야 함.
@Test
@Rollback(false)
public void 회원가입() throws Exception {
// given
Member member = new Member();
member.setName("kim");
// when
Long saveId = memberService.join(member);
// then
assertEquals(member, memberRepository.findOne(saveId));
}
실질적으로 가장 좋은 테스트 방법은, 따로 메모리 DB에서 테스트를 하는 것! (왓슨)
@Test
public void 중복_회원_예외() throws Exception {
// given
Member member1 = new Member();
member1.setName("kim");
Member member2 = new Member();
member2.setName("kim");
// when
memberService.join(member1);
try{
memberService.join(member2); // 예외가 발생해야 한다!!
} catch(IllegalStateException e){
return;
}
// then
fail("예외가 발생해야 한다. Test 실패");
}
이렇게 예외처리를 받아야하는 Test의 경우, expected
로 설정 가능
@Test(expected = IllegalStateException.class)
public void 중복_회원_예외() throws Exception {
// given
Member member1 = new Member();
member1.setName("kim");
Member member2 = new Member();
member2.setName("kim");
// when
memberService.join(member1);
memberService.join(member2); // 예외가 발생해야 한다!!
// then
fail("예외가 발생해야 한다.");
}
test 폴더에 resources
디렉토리 만들면, Test 는 해당 폴더 내의 자원을 먼저 접근함.
즉, DB를 따로 쓸 수 있게됨.
h2에서 제공해주는 메모리 모드를 활용!
실제 < application.yml
>
spring:
datasource:
url: jdbc:h2:tcp://localhost/~/jpashop_230403 # ;MVCC=TRUE : 권장
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
Test - resources 의 < application.yml
>
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
jdbc:h2:mem:test
를 사용해서, 테스트는 h2 를 메모리모드로 띄워서 활용!
기존 h2 서버를 꺼놔도, 잘 돌아감!!
Test의 경우, Spring boot에서는 비어있으면 메모리 모드로 지원을 해줌 (이렇게 해도 됨)
기본 설정은 create-drop
이다!!
- 설정을 따로 가져가는 것!!