250x250
Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- eager
- FetchType
- 일대다
- 지연로딩
- 다대다
- 이진탐색
- shared lock
- 다대일
- dfs
- 동적sql
- 연결리스트
- execute
- PS
- CHECK OPTION
- querydsl
- 백트래킹
- 즉시로딩
- 유니크제약조건
- SQL프로그래밍
- fetch
- 연관관계
- 힙
- 비관적락
- 낙관적락
- JPQL
- 스프링 폼
- exclusive lock
- BOJ
- 스토어드 프로시저
- 데코레이터
Archives
- Today
- Total
흰 스타렉스에서 내가 내리지
테스트 환경에서 엔티티 비교 본문
728x90
# 엔티티 비교
같은 영속성 컨텍스트에서 엔티티를 조회하면 다음 코드와 같이 항상 같은 엔티티 인스턴스를 반환한다.
Member member1 = em.find(Member.class, "1L");
Member member2 = em.find(Member.class, "2L");
assertTrue(member1 == member2);
# 영속성 컨텍스트가 같을 때 엔티티 비교
@Transactional
public class MemberServiceTest{
...
@Test
public void join(){
//Given
Member member = new Member("member1");
//When
Long saveId = memberService.join(member);
//Then
Member findMember = memberRepository.findOne(saveId);
assertTrue(member == findMember); // 참조값 비교
}
}
@Transactional
public class MemberService{
...
public Long join(Member member){
...
memberRepository.save(member);
return member.getId();
}
}
- 테스트 클래스에 @Transactional 이 선언되어 있으면 트랜잭션을 먼저 시작하고 테스트 메소드를 실행한다.
- 테스트 클래스의 join() 메소드는 이미 트랜잭션 범위에 들어 있고, 이 메소드가 끝나면 트랜잭션이 종료된다.
- 따라서, join() 메서드에서는 항상 같은 트랜잭션과 같은 영속성 컨텍스트에 접근한다.
- 참고) 1트랜잭션 1PC
- 주목해야 할 것은 저장한 회원과 회원 리포지토리에서 찾아온 엔티티가 완전히 같은 인스턴스라는 점이다.
- 이것은 같은 트랜잭션 범위에 있으므로 같은 영속성 컨텍스트를 사용하기 때문이다.
- 영속성 컨텍스트가 같으면 엔티티를 비교할 때 다음 3가지 조건을 모두 만족한다.
- 동일성 (identical) : ==
- 동등성 (equivalent) : equals()
- 데이터베이스 동등성 : @Id 인 데이터베이스 식별자가 같다
테스트에도 @Transactional 이 있고 서비스에도 @Transactional 이 있다.
기본 전략은 먼저 시작된 트랜잭션이 있으면 그 트랜잭션을 그대로 이어 받아 사용하고 없으면 새로 시작한다.
테스트 클래스에 @Transactional 을 적용하면 테스트가 끝날 떄 트랜잭션을 커밋하지 않고 트랜잭션을 강제로 롤백하낟.
그래야 데이터베이스에 영향을 주지 않고 테스트를 반복해서 할 수 있기 때문이다.
문제는 롤백시에는 영속성 컨텍스트를 플러시하지 않기 때문에 실행된 SQL 을 콘솔에서 확인할 수 없다.
어떤 SQL 이 실행되는지 콘솔을 통해 보고 싶으면 테스트 마지막에 em.flush() 를 강제로 호출하면 된다.
# 영속성 컨텍스트가 다를 때 엔티티 비교
//@Transactional // 테스트에서 트랜잭션을 사용하지 않는다
public class MemberServiceTest{
...
@Test
public void join(){
//Given
Member member = new Member("member1");
//When
Long saveId = memberService.join(member);
//Then
Member findMember = memberRepository.findOne(saveId);
assertTrue(member == findMember); // 실패한다.
}
}
@Transactional
public class MemberService{
...
public Long join(Member member){
...
memberRepository.save(member);
return member.getId();
}
}
@Transactional
public class MemberRepository{
...
}
- 테스트 클래스에 @Transactional 이 없고 서비스에만 @Transactional 이 있다.
- 테스트 코드에서 memberService.join(member) 를 호출해서 회원가입을 시도하면 서비스 계층에서 트랜잭션이 시작되고 PC1 이 만들어진다.
- 서비스 계층이 끝날 떄 트랜잭션이 커밋되면서 영속성 컨텍스트가 플러시된다. 이때 트랜잭션과 영속성 컨텍스트가 종료된다. 따라서 member 엔티티 인스턴스는 준영속 상태가 된다.
- 테스트 코드에서 memberRepository.findOne(saveId) 를 호출해서 저장한 엔티티를 조회하면 리포지토리 계층에서 새로운 트랜잭션이 시작되면서 새로운 PC2 가 생성된다.
- DB 에서 조회된 엔티티를 PC2 에 저장하고 반환한다.
- memberRepository.findOne() 메소드가 끝나면서 트랜잭션이 종료되고 PC2 도 종료된다.
- member 와 findMember 는 각각 다른 영속성 컨텍스트에서 관리되었기 때문에 둘은 다른 인스턴스다.
- 따라서 동일성(==) 검사가 실패한다.
-→ 엔티티를 비교할 때는 비즈니스 키를 활용한 동등성(equals()) 비교를 권장한다.
'JPA' 카테고리의 다른 글
[JPA] N+1 문제를 피할 수 있는 다양한 방법 (1) | 2024.04.28 |
---|---|
프록시로 조회해도 영속성 컨텍스트는 영속 엔티티의 동일성을 보장한다. (0) | 2024.04.28 |
[Spring-data-JPA] 벌크성 수정 쿼리 (0) | 2024.04.25 |
[JPA] Spring-data-JPA 의 메소드 이름으로 쿼리 생성 (0) | 2024.04.25 |
[JPA] 엔티티의 데이터를 변환해서 DB 에 저장하는 @Converter (0) | 2024.04.25 |