Spring/JPA

[스프링] JPA_S4 : 회원 도메인 개발

ajeong7038 2023. 11. 14. 17:32

 

`실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발` 강의를 듣고 정리한 자료입니다

 

✨ 회원 리포지토리 개발

@PersistanceContext

- JPA가 제공하는 표준 애노테이션

- EntityManager 위에 @PersistanceContext 애노테이션을 달게 되면 스프링이 EntityManager 생성 및 주입을 자동으로 해준다

- `@Autowired` 대신 `@PersistanceContext`를 써야 injection 가능

// 엔티티 매니저 팩토리 직접 주입
@PersistenceUnit
private EntityManagerFactory emf;

 

findAll()

public List<Member> findAll() {
    return em.createQuery("select m from Member m", Member.class)
            .getResultList();
}

 

- createQuery로 JPQL을 날리게 된다

- JPQL과 SQL이 기능적으로는 거의 동일하지만 SQL은 테이블 대상, JPQL은 엔티티 객체를 대상으로 쿼리를 날린다

    -> from의 대상이 엔티티


✨ 영속성 컨텍스트

- 엔티티를 영구 저장하는 환경

- 엔티티 매니저를 통해 엔티티를 저장·조회 시 엔티티 매니저가 영속성 컨텍스트에 엔티티를 보관하고 관리하게 된다.

em.persist(member);

 

- 엔티티 매니저 (em)를 사용해 회원 엔티티를 영속성 컨텍스트에 저장한다

특징

1. 엔티티 매니저 생성 시 영속성 컨텍스트도 하나 함께 생성

2. 엔티티 매니저를 통해 영속성 컨텍스트 접근 및 관리 가능

- 영속성 컨텍스트는 key & value를 가지고 있다

    -> 강의에서는 key 값이 id 값


✨ 회원 서비스 개발

- `@Service` 애노테이션을 달면 컴포넌트 스캔의 대상이 된다

- 부하를 줄여주므로 읽기에는 가급적 readOnly를 true로 설정해줄 것

@Transactional(readOnly = true)

 

1. 기본

@Transactional
public class MemberService {
	public Long join(Member member) { }
    
    public List<Member> findMembers() { }
    public Member findOne(Long memberId) { }
}

 

2. 트랜잭션 readOnly 활용

public class MemberService {
	@Transactional
	public Long join(Member member) { }
    
    @Transactional(readOnly = true)
    public List<Member> findMembers() { }
    
    @Transactional(readOnly = true)
    public Member findOne(Long memberId) { }
}

 

3. 코드 정리

@Transactional(readOnly = true)
public class MemberService {
	@Transactional // 우선권을 가짐
	public Long join(Member member) { }
    
    public List<Member> findMembers() { }
    public Member findOne(Long memberId) { }
}

 

-> 그때그때 맞춰서 트랜잭션 달 것

injection

@Autowired
private MemberRepository memberRepository;

 

- 이렇게 쓸 경우에 변경이 불가하므로 밑과 같은 식으로 setter injection을 사용한다

 

// setter injection
private MemberRepository memberRepository;

@Autowired
public void setMemberRepository(MemberRepository memberRepository) {
    this.memberRepository = memberRepository;
}

 

- 테스트 코드 작성 시 메소드를 통해 직접 주입이 가능하다

- 누군가 바꿀 일이 없으므로 자리 차지를 한다 (??)

- 요즘 추세는 `생성자 injection`

private final MemberRepository memberRepository; // 바뀔 일이 없기 때문에

@Autowired // 없어도 됨
public MemberService(MemberRepository memberRepository) {
    this.memberRepository = memberRepository;
}

 

- 중간에 MemberRepository를 변경할 수 없다 -> setter injection 단점 극복

- 테스트 케이스 작성 시 직접 주입을 해줘야 한다

    -> 오류 메시지가 떠 놓치지 않고 무엇을 의존하는지 명확하게 알 수 있다

- final 키워드를 넣으면 컴파일 시 자동으로 체크되기 때문에 좋다

롬복을 활용한 의존 관계 확장

- `@AllArgsConstructor` : 모든 필드를 가지고 생성자를 만들어 줌

- `@RequiredArgsConstructor` : `final`이 있는 필드만을 가지고 생성자를 만들어 줌

MemberRepository injection 변경

@PersistenceContext
private EntityManager em;

 


✨ 회원 기능 테스트

회원가입 테스트

- 스프링 부트에 올려 테스트하기 위해 사용하는 애노테이션들

@RunWith(SpringRunner.class) // junit과 스프링을 엮어서 실행
// 인데 위에 @RunWith는 Junit4이고, Junit5에서는 문법이 바뀌었다.
@ExtendWith(SpringExtension.class) // ==@RunWith~
@SpringBootTest // 스프링 컨테이너 안에서 테스트를 돌린다
@Transactional // 롤백
public class 클래스명 { }

 

- persist 한다고 해서 insert문이 나가지는 않는다

- `@Transactional`이 테스트 케이스에 있으면 커밋 대신 롤백을 하므로 insert문을 보고 싶을 때는 @Rollback(false)로 설정할 것

- em.flush(); 로도 insert문 확인 가능

- `@Rollback(false)` 로 하면 db에 반영되는 것이 눈으로 보인다

중복 회원 예외 테스트

- validateDuplicateMember (중복 회원 검증) 이 잘 동작하는지 검증

fail("예외가 발생해야 한다.");

 

- `fail()` : 예외를 발생시킨다

    -> java.lang.AssertionError: 예외가 발생해야 한다

 

1. try-catch문 활용

@Test
public class 중복_회원_예외() throws Exception {

	try {
    	memberService.join(member2); // 예외가 발생해야 함 (같은 이름을 넣었으므로)
	} catch (IllegalStateException e) {
    	return;
	}
}

 

2. 애노테이션 활용

@Test(expected = IllegalStateException.class)
public class 중복_회원_예외() throws Exception {
	memberService.join(member2); // 예외가 발생해야 함 (같은 이름을 넣었으므로)
}

 

- IllegalStateException 예외를 잡아준다

Memory DB

- DB를 켜지 않아도 테스트를 돌릴 수 있다

1. test 디렉토리 밑 `resources` 디렉토리 생성

2. application.yml 파일 복제

    -> main\resources\application.yml 보다 test\resources\application.yml이 더 우선시된다

3. spring: datasource: url을 Memory DB로 교체


✨ 질문 (답은 추후에 추가할 것...)

- Repository 정확히 하는 일이 뭐지?

- `@Repository`를 달면 컴포넌트 빈으로 자동 등록?

- throw와 throws 차이

- 예외 처리 시 의존 관계 발생 -> ?

    -> https://velog.io/@mooh2jj/%EC%9E%90%EB%B0%94-%EC%98%88%EC%99%B8%EC%B2%98%EB%A6%ACtry-catch-throw-throws

- `@Transactional` 사용? => 데이터 변경 및 조회?

- 의존성 주입...?

- setter injection 단점

- JPA, Spring -> 커밋, 롤백

- flush()


✨ 참고 자료

- https://velog.io/@neptunes032/JPA-%EC%98%81%EC%86%8D%EC%84%B1-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8%EB%9E%80