tonykang22 / study

0 stars 0 forks source link

[JPA] 15장. 고급 주제와 성능 최적화 #158

Open tonykang22 opened 1 year ago

tonykang22 commented 1 year ago

고급 주제와 성능 최적화

15.1 예외 처리

15.1.1 JPA 표준 예외 정리


image

image

Reference: https://openjpa.apache.org/builds/1.0.2/apache-openjpa-1.0.2/docs/manual/jpa_overview_arch.html



15.1.2 스프링 프레임워크의 JPA 예외 변환

JPA 예외 스프링 변환 예외
PersistenceException org.springframework.orm.jpa.JpaSystemException
NoResultException org.springframework.dao.EmptyResultDataAccessException
NonUniqueResultException org.springframework.dao.IncorrectResultSizeDataAccessException
LockTimeoutException org.springframework.dao.CannotAcquireLockException
QueryTimeoutException org.springframework.dao.QueryTimeoutException
EntityExistsException org.springframework.dao.DataIntegrityViolationException
EntityNotFoundException org.springframework.orm.jpa.JpaObjectRetrievalFailureException
OptimisticLockException org.springframework.orm.jpa.JpaOptimisticLockingFailureException
PessimisticLockException org.springframework.dao.PessimisticLockingFailureException
TransactionRequiredException org.springframework.dao.InvalidDataAccessApiUsageException
RollbackException org.springframework.transaction.TransactionSystemException
JPA 예외 스프링 변환 예외
IllegalStateException org.springframework.dao.InvalidDataAccessApiUsageException
IllegalArgumentException org.springframework.dao.InvalidDataAccessApiUsageException



15.1.3 스프링 프레임워크에 JPA 예외 변환기 적용

    @Bean
    public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
        return new PersistenceExceptionTranslationPostProcessor;
    }
@Repository
public class NoResultExceptionTestService {

    @PersistenceContext EntityManager em;

    public member findMember() throws javax.persistence.NoResultException {
        return em.createQuery("select m from Member m", Member.class).getSingleResult();
    }
}



15.1.4 트랜잭션 롤백 시 주의사항



15.2 엔티티 비교

15.2.1 영속성 컨텍스트가 같을 때 엔티티 비교

image



@Transactional
@SpringBootTest(properties = "spring.profiles.active:local")
public class MemberServiceTest {

    @Autowired MemberService memberService;
    @Autowired MemberRepository memberRepository;

    @Test
    public void 회원가입() throws Exception {
        // given
        Member member = new Member("kang");

        // when
        Long saveId = memberService.join(member);

        // then
        Member findMember = memberRepository.findOne(saveId);
        assertTrue(member == findMember); // 참조값 비교
    }
}

@Transactional
public class MemberService {

    @Autowired MemberRepository memberRepository;

    public Long join(Member member) {
        ...
        memberRepository.save(member);
        return member.getId();
    }
}

@Repository
public class MemberRepository {

    @PersistenceContext
    EntityManager em;

    public void save(Member member) {
        em.persist(member);
    }

    public Member findOne(Long id) {
        return em.find(Member.class, id);
    }
}

image



15.2.2 영속성 컨텍스트가 다를 때 엔티티 비교

image


@Transactional
@SpringBootTest(properties = "spring.profiles.active:local")
public class MemberServiceTest {

    @Autowired MemberService memberService;
    @Autowired MemberRepository memberRepository;

    @Test
    public void 회원가입() throws Exception {
        // given
        Member member = new Member("kang");

        // when
        Long saveId = memberService.join(member);

        // then
        // findMember는 준영속 상태
        Member findMember = memberRepository.findOne(saveId);
        assertTrue(member == findMember); // 테스트 실패
    }
}

@Transactional
public class MemberService {

    @Autowired MemberRepository memberRepository;

    public Long join(Member member) {
        ...
        memberRepository.save(member);
        return member.getId();
    }
}

@Repository
@Transactional // findOne 호출 시 새로운 트랜잭션이 시작된다.
public class MemberRepository {

    @PersistenceContext
    EntityManager em;

    public void save(Member member) {
        em.persist(member);
    }

    public Member findOne(Long id) {
        return em.find(Member.class, id);
    }
}


image


  1. 테스트 코드에서 memberService.join(member) 호출 시
    • 서비스 계층에서 트랜잭션 시작되며 영속성 컨텍스트1이 만들어진다.
  2. memberRepository에서 em.persist() 호출해 member 엔티티를 영속화한다.
  3. 서비스 계층이 끝나면 트랜잭션이 커밋되면서 영속성 컨텍스트가 플러시된다.
    • 트랜잭션과 영속성 컨텍스트1 종료 (member 엔티티는 준영속 상태)
  4. 이후 memberRepository.findOne(saveId) 호출 시
    • 리포지토리 계층에서 새로운 트랜잭션이 시작되면서 영속성 컨텍스트2가 만들어진다.
  5. 저장된 회원을 조회하지만 영속성 컨텍스트2에는 찾는 회원이 존재하지 않는다.
  6. 데이터베이스에 접근해 회원을 찾아온다.
  7. 해당 회원을 영속성 컨텍스트에 보관하고 반환한다.
  8. memberRepository.findOne(saveId) 메소드가 끝나면 트랜잭션, 영속성 컨텍스트는 종료된다.



정리



15.3 프록시 심화 주제


예시1

  1. em.getReference()member1을 프록시로 조회한다.
  2. em.find()로 같은 member1을 조회한다.



@Test
void 영속성컨텍스트와_프록시() {

    Member newMember = new Member("member1", "회원1");
    em.persist(newMember);
    em.flush();
    em.clear();

    Member refMember = em.getReference(Member.class, "member1");
    Member findMember = em.find(Member.class, "member1");

    // refMember Type = class jpabook.advanced.Member_$$_jvst843_0
    // findMember Type = class jpabook.advanced.Member_$$_jvst843_0
    System.out.println("refMember Type = " + refMember.getClass());
    System.out.println("findMember Type = " + findMember.getClass());

    assertTrue(refMember == findMember); // 성공
}



예시2

@Test
void 영속성컨텍스트와_프록시2() {

    Member newMember = new Member("member1", "회원1");
    em.persist(newMember);
    em.flush();
    em.clear();

    Member findMember = em.find(Member.class, "member1");
    Member refMember = em.getReference(Member.class, "member1");

    // refMember Type = class jpabook.advanced.Member
    // findMember Type = class jpabook.advanced.Member
    System.out.println("refMember Type = " + refMember.getClass());
    System.out.println("findMember Type = " + findMember.getClass());

    assertTrue(refMember == findMember); // 성공
}



15.3.2 프록시 타입 비교


예시

@Test
void 프록시_타입비교() {

    Member newMember = new Member("member1", "회원1");
    em.persist(newMember);
    em.flush();
    em.clear();

    Member refMember = em.getReference(Member.class, "member1");

    // refMember Type = class jpabook.advanced.Member
    System.out.println("refMember Type = " + refMember.getClass());

    assertFlase(Member.class == refMember.getClass()); // 성공
    assertTrue(refMember instanceof Member); // 성공
}



15.3.3 프록시 동등성 비교


예시

image

@Entity
public class Member {

    @Id
    private String id;
    private String name;

    ...

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null) return false;
        if (this.getClass() != obj.getClass()) return false;

        Member member = (Member) obj;

        if (name != null ? !name.equals(member.name) : member.name != null) return false;

        return true;
    }

    @Override
    public int hashCode() {
        return name != null ? name.hashCode() : 0;
    }
}
@Test
void 프록시_동등성비교() {

    Member saveMember = new Member("member1", "회원1");
    em.persist(saveMember);
    em.flush();
    em.clear();

    Member newMember = new Member("member1", "회원1");
    Member refMember = em.getReference(Member.class, "member1");

    assertTrue(newMember.equals(refMember)); // 실패
}

image

image



15.3.4 상속관계와 프록시

image


예시

@Test
void 부모타입으로_프록시조회() {

    // given
    Book saveBook = new Book();
    saveBook.setName("jpaBook");
    saveBook.setAuthor("kim");
    em.persist(saveBook);

    em.flush();
    em.clear();

    // when
    Item proxyItem = em.getReference(Item.class, saveBook.getId());
    System.out.println("proxyItem = " + proxyItem.getClass());

    if (proxyItem instanceof Book) { // false
        System.out.println("proxyItem instanceof Book");
        Book book = (Book) proxyItem; // ClassCastException 발생
        System.out.println("책 저자 = " + book.getAuthor());
    }

    // then
    assertFalse(proxyItem.getClass() == Book.class);
    assertFalse(proxyItem instanceof Book);
    assertTrue(proxyItem instanceof item);
}

image