이 글은 인프런의 '김영한'님 강의를 참조하여 작성하였습니다.
@RequiredArgsConstructor //여기 리포지토리에서도 사용 가능!
@Repository //컴포넌트 스캔에 의해_ 자동으로 스프링 빈에 등록되어 관리
public class MemberRepository {
private final EntityManager em;
/*
//스프링이 jpa의 엔티티매니저를 자기가 주입해줌.
@PersistenceContext //=>주입 받을 수 있다.
private EntityManager em; //스프링이 em을 만들어서 인젝션 해줌
*/
//트랜잭션이 commit이 되는 시점에 DB에 insert 됨.
public void save(Member member){
//영속성 컨테스트에 member엔티티를 저장
em.persist(member);
}
//단건 조회
public Member findOne(Long id){
//해당 멤버를 찾아서 반환.
//find(타입, PK)
return em.find(Member.class, id);
}
//전체회원 목록 조회 등
public List<Member> findAll(){
//em.createQuery("JPQL", 반환타입)
//JPQL은 from이 엔티티를 대상으로 쿼리를 한다.-> 이 엔티티를 조회해
//SQL은 테이블을 대상으로 쿼리 ->테이블을 조회해.
//JPQL과 SQL은 동일함_>결국 SQL로 변환된다.
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();
}
}
wan-blog님과 HeeJeong Kwon님의 블로그를 참조하여 작성하였습니다.
👀EntityManger
엔티티에 관련된 모든 일을 처리하며 데이터베이스에 Entity를 (등록,수정,삭제, 조회) 할 수 있다.
=> 동시성 문제로 인해 스레드 간 공유를 해선 안되므로 일반적으로 위처럼 스프링에서 관리하는 EntityManager em
선언하여 사용한다.
-Entity를 영구 저장하는 환경.
-EntityManager
로 Entity를 저장하거나 조회하면 '영속성 컨텍스트'에 엔티티를 보관 및 관리한다.
EntityManager.persist(엔티티)
는
즉 실제 DB에 저장하진 않고,persist()
시점에 Entity를 영속성 컨텍스트에 저장하겠다는 의미이다.- 결과적으로 트랜잭션의
commit
시점때 영속성 컨텍스트의 정보들이 DB에 쿼리로 날라감.
⋇JPQL 정의
jpa에서 모든 DB 데이터를 객체로 변환해서 검색이 어렵기에 애플리케이션이 필요하다하는 데이터만 DB에서 가져오기 위해 SQL을 추상화한 JPQL을 쓰게 되었다.
- 결과 조회 :
쿼리.getResultList()
- 결과가 하나 이상, 리스트로 결과를 반환.- 파라미터 바인딩 :
쿼리.setParameter("조건이름",파라미터)
(1)이름 기준
- query에서where 조건=":검색명"
으로 조건절 안에 ' : '을 넣어줘야 한다.
(2)위치 기준
- query에서where 조건="?검색위치"
으로 조건절 안에 ' ? '을 넣어줘야 한다.
//자동으로 스프링 빈 등록
@Service
//읽기인 곳은 (readOnly = true) , 아닌 곳은 @Transactional만
//이 클래스의 public메소드는 주로 읽기가 많기에 (readOnly = true)를 넣어줌
//(readOnly = true)가 있는 곳은 jpa가 '조회하는 곳에서' 좀 더 성능이 최적화한다.
@Transactional(readOnly = true)
//3)더더 발전한 사용법. - 롬복을 사용(강사님이 추천)
//테스트케이스에서도 유용.
//final 필드에 있는 애들만 가지고 생성자 만들어줌
@RequiredArgsConstructor
public class MemberService {
//final 권장- 생성자에 값을 세팅했는지 확인 가능.
private final MemberRepository memberRepository;
/*
//2) 발전한 요새 사용법- '생성자 주입식(인젝션)'
//장) 테스트 케이스 작성시, 생성시점을 명확히 알 수 잇음.
//생성자가 하나일시 -> @Autowired 생략 가능.
@Autowired
public MemberService(MemberRepository memberRepository){
this.memberRepository = memberRepository;
}
*/
/*
//1) 스프링이 빈에서 memberRepository을 인젝션함 => 필드 인젝션
단점: 테스트할때 변경을 할 수가 없음.
@Autowired
private MemberRepository memberRepository;
*/
/**
* 회원 가입
*/
@Transactional //쓰기가 주인 경우 (readOnly = true) 필요x.
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();
}
//Id로 회원 단건 조회
public Member findOne(Long memberId){
return memberRepository.findOne(memberId);
}
}
◼ 주 메서드
¹회원가입 ²회원가입 내에 중복회원 검증 ³전체회원 조회 ⁴Id로 단건 회원 조회
[kdhyo님의 블로그를 참조하였습니다.]
데이터변경을 위해 되도록 @Transactional 안에서 실행 되어야 한다.
그래서 public 메소드가 쓰기가 '주'인 곳만 따로 @Transactional
을 지정해주고 전체적으로는 읽기전용으로 지정해줬다.
- 조회가 주인 곳은
@Transactional(readOnly = true)
을 넣어줌.- 쓰기가 주인 곳은
@Transactional
만!
(단)테스트시 액세스 되어 있어 바꾸기 어려움
(장) ¹테스트 케이스때 유용 ²어떤 것이 누구에게 의존하는지 확인이 가능
³변경이 불가한 안전한 객체 생성 ⁴생성자가 하나일시, @Autowired
생략가능
final
로 선언해주면, 컴파일 시점에memberRepository
를 설정했는지 오류를 체크해줄 수 있다.
-> 보통이라면 기본 생성자를 추가할때나 확인이 가능
나아가 2번 생성자 주입에서 lombok을 사용하면 더 깔끔하다.
@RequiredArgsConstructor
을 클래스에 달아주면 final
이 있는 필드만 가지고 생성자를 만들어 준다.
-> 기본적으로 인젝션(주입) 하면서 생성자에 세팅하고 끝! 인 애들만 final
필드로 잡아주면 좋다.
📣또한 Repository에서도..
본래라면 @PersistenceContext
로 EntityManager
을 의존성 주입 해줬는데 최근 스프링 데이터 JPA에서는 @Autowired
로도 의존성 주입을 가능하게 해줬다.
//메모리모드로 DB까지 엮어서 테스트하고자
@RunWith(SpringRunner.class)
//스프링 부트를 띄우고 테스트 하는 것 -> 없으면 @AutoWired 못함
@SpringBootTest
//데이터 변경을 위해 -rollback등...
//테스트케이스에서의 @Transactional은 기본적으로 테스트가 끝나면 Rollback 기능을 함
//왜 롤백을 하는가? 테스트이므로 DB에 이 데이터가 남으면 안되기 때문
//Rollback을 한단 것 자체가 jpa입장에선 DB를 다 버린다는 것..
@Transactional
public class MemberServiceTest {
//참조를 위해
@Autowired MemberService memberService;
@Autowired MemberRepository memberRepository;
//기본적으로 persist()만으로 db에 insert문은 안나감.
//보통 트랜잭션이 커밋될때 db에 insert가 나간다.
@Test
//@Rollback(false) -> 커밋함 -> insert쿼리 볼 수 잇음.
public void 회원가입() throws Exception {
//given ~이 주어졌을때
Member member = new Member();
member.setName("kim");
//when(~을 실행하면)
Long saveId = memberService.join(member);
//then (~의 결과가 나옴)
//em.flush(); //영속성 컨텍스트에 있는 변경 등을 DB에 반영함(굳이 롤백이지만 insert보고 싶다면)
assertEquals(member, memberRepository.findOne(saveId));
}
//너무 복잡하여 사용(expected = Ille)
//member2에서 예외가 발생해 터져서 나간애가 (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); //예외가 발생해야 함
/* 코드가 너무 복잡.
try{
memberService.join(member2); //예외가 발생해야 함
}catch (IllegalStateException e) {
return;
}
*/
//then
//fail() -> 코드가 여기에 오면 잘못된 것이므로 fail()을 띄움.
fail("예외가 발생해야 한다.0");
}
}
멤버에서 테스트가 필요할 기능 _ 1) 회원가입 2)중복 회원인지
👀 jUnit은 독립된 단위테스트를 지원해주는 프레임워크.
백기선님의 블로그를 참조하였습니다.
4.xx 버전에서 테스트 실행방법을 확장하는 방법 중 하나로 테스트를 지원하는 어노테이션이다.
그래서 @RunWith(SpringRunner.class)
은 스프링부트를 테스트로 작성할때 주로 쓰였다.
but..현재는
- 스프링부트 JUnit5 부터는 @RunWith를 쓸 필요가 없어짐 ->쓴다면
@ExtendWith(SpringExtension.class)
를 이용해 Extension 구현체를 지정해줘야 함.
- 📣 하지만 이마저도 이미 스프링 부트가 (모든 테스트용 애노테이션)@SpringBootTest
에 적용해서@ExtendWith(SpringExtension.class)
생략이 가능하다
Namjun Kim님의 블로그를 참조하였습니다.
- 통합 테스트를 제공하는 '스프링 부트 테스트 어노테이션' 이다.
- 스프링 부트는
@SpringBootTest
를 이용해 테스트에 필요한 모든 의존성을 제공한다. ->테스트를 위한 빈을 생성.@MockBean
으로 정의된 빈을 찾아 교체해준다.
영속성 컨텍스트에 있는 변경사항 등을 DB에 반영해준다.
insert
쿼리가 안나올까?
물론 이처럼 @Rollback(false)
로 롤백이 아닌 커밋으로 바꿔버릴 수도 있다...
스프링에서는 @Transactional
이 테스트 케이스에 있으면 기본적으로 RollBack을 한다.
►RollBack을 하면 jpa 입장에서는 일정시점으로 다시 되돌리는 기능으로 DB에 일정 부분을 다시 원래 상태로 되돌린다는 것이다.
그 이유는 테스트를 한다는 것은 정상적으로 작동을 하는지 시험을 하는 것으로 DB에 데이터가 남아선 안되기 때문이다.
위의 사진에선 member2
의 중복회원으로 인해 예외 발생시 try catch
문으로 작성하기엔 코드가 복잡하다.
=> 이것을 해결하기 위해 @Test(expected= 예외 클래스)
로 예외 클래스를 지정해주면 테스트에서 지정한 예외가 발생하여야 테스트가 정상적으로 작동한다.
테스트 케이스는 격리된 환경에서 실행되고, 끝나면 데이터를 초기화 하는 것을 권장한다.=> '메모리 DB'의 사용이 이상적.
1) 테스트 케이스를 위한 스프링 환경과 일반적인 애플리케이션 실행 환경이 다르므로 application.yml
파일 개별 생성.
spring:
# datasource:
# url: jdbc:h2:mem:testdb
# username: sa
# password:
# driver-class-name: org.h2.Driver
# jpa:
# hibernate:# ddl-auto: create-drop
# properties:
# hibernate:
# show_sql: true
# format_sql: true
# open-in-view: false
logging.level:
org.hibernate.SQL: debug
# org.hibernate.type: trace
2) 스프링 부트는 이 처럼 datasource 설정이 따로 없다면, 기본적으로 '메모리 DB'를 사용.
추추가)create-drop
마지막 종료시점에 drop 쿼리를 날려줌 (꼭 필요치는 뭐)