1. JPA 연관관계 원리
🎁 우리가 정리하고 가야할 키워드 = ORM
, Hibernate
, JPA
, Spring Data JPA
🌱 JPA 정리 (ORM
, Hibernate
, JPA
)
👑 Spring Data JPA
👩❤️👨 JPA 연관관계 매핑
2. 자주 발생하는 JPA 에러 및 조치방법
1. 다대일 관계에서 주로 발생 하는 에러
2. 연관 객체의 변경사항이 저장되지 않는 에러
3. 연관 객체의 변경사항이 업데이트 되지 않는 에러
4. 연관관계 조회시 발생하는 N+1 에러
5. save() 이후 변경사항이 반영되지 않는 에러
6. OneToMany 단방향 매핑 이슈
7. 테스트코드는 통과했는데 런타임에 발생한 초기화 에러
3. JPA 연관관계 에러 안나게 개발하는 법
<aside>
💡 JPA의 내부 작동 흐름에 대해 이해하는 것이 정말 중요하다.
- 영속성 컨텍스트의 흐름에 대한 이해가 부족하면 문제 발생 확률이 높아지고, 문제 원인도 못찾을 수 있다.
- 양방향 연관관계에서는 객체의 값을 INSERT할 때, 안전하게 두 객체 모두에게 반영해주자.
- 두 객체의 매핑 메서드를 사용하거나.
- 연관관계 편의 메서드를 사용하자. 이 때에는, 두 객체에서 모두 삽입하지 않도록 주의하자. 두 번 INSERT될 수 있다.
</aside>
<aside>
💡 아래의 행위를 해주지 않는다면 db에 쿼리가 날라가지 않아 db에 반영이 바로 되지 않음을 꼭 기억!
- entityManager에서 flush를 해준다. (
XXXrepository.save()
, saveAndFlush
)
- transaction commit을 해준다. (
@Transactional
)
- JPQL을 사용한다. (
@Query
또는 EntityManager.createQuery()
로 직접 쿼리짜는방법)
💁♂️ Raw JPA 관점에서 순서대로 보자면
persist(), merge() > (영속성 컨텍스트에 저장된 상태)
> flush() > (DB에 쿼리가 전송된 상태)
> commit() > (DB에 쿼리가 반영된 상태)
💁♀️ 이제, Raw JPA를 추상화시킨 SpringDataJpa 관점에서 연결해서 보자면
- save() 메소드
- persist() 까지 수행하고 (업데이트일 경우 merge() )
- save() 메소드에 달린 내부적인 @Transactional 어노테이션에 의해 flush, commit 까지 수행됩니다.
- saveAndFlush() 메소드
- persist(), flush() 까지 수행하고
- saveAndFlush() 메소드에 달린 내부적인 @Transactional 어노테이션에 의해 commit() 까지 수행됩니다.
@Transactional 어노테이션이 해당 함수 종료시점에 flush, commit을 모두 수행하며
Repository 기본 구현체인 SimpleRepository 클래스의 save 메소드에 보면 @Transactional 어노테이션이 달려있기 때문입니다. (깃헙 코드 링크)
결론은, flush 와 commit 사이 즉, DB에 쿼리가 전송된 상태
에서 추가동작이 필요한경우 SimpleRepository 대신 Repository 를 개별로 구현하여 @Transactional 이 달리지 않은 saveAndFlush() 를 구현하고, 그렇지 않다면 기본적으로 save() 메소드만 사용합니다.
</aside>
<aside>
💡 지연 로딩(LAZY)를 우선적으로 사용한다.
- N+1 에러 방지를 위해 지연 로딩 (LAZY) 모드로 사용을 하고 성능 최적화가 필요한 부분에서는 Fetch 조인을 사용한다.
</aside>
<aside>
💡 OneToMany 매핑은 양방향으로 해준다.
- 단방향으로 매핑하면 자식 엔티티가 관리하는 외래 키가 다른 테이블에 있음 => 작업한 Entity가 아닌 다른 Entity에서 쿼리문이 나가는 경우가 있어 헷갈림
- 양방향으로 하면 외래 키가 같은 테이블에 생김
- 단방향으로 매핑하면 불필요한 쿼리문이 발생(update 등)
- 양방향으로 하면 연관관계 정보를 통해 한번에 쿼리 수행
</aside>
<aside>
💡 save() 이후에는 반환된 인스턴스를 사용해야 한다.
- save 할때 넘겨준 객체는 save 이후부터는 EntityManager가 관리하고 있는 대상이 아니기 때문에(준영속성 상태임) 이 객체의 값을 바꿔봐야 쿼리에 영향을 미치지 않는다.
- save 메소드 응답값으로 오는 객체가 영속성 상태의 객체이니 이것을 사용해야 한다.
</aside>