자바 ORM 표준 JPA 프로그래밍 - 김영한 책 내용을 정리한 내용입니다.
애플리케이션에서 SQL을 직접 다룰 때 발생하는 문제점
애플리케이션은 자바라는 객체지향 언어로 개발하고 데이터는 관계형 데이터베이스에 저장해야한다면, 패러다임 불일치 문제를 개발자가 중간에서 해결해야한다.
객체는 상속이란 기능을 갖고 있지만 테이블은 상속이란 기능이 없다.
데이터베이스 모델링에서 이야기하는 슈퍼타입 서브타입 관계를 사용하면 유사하게 설계할 수 있다. 그러나 조회하는 것부터 쉬운 일이 아니며 이 과정은 패러다임 불일치를 해결하려고 소모하는 비용이다.
-> JPA는 자바 컬렉션에 객체를 저장하듯 JPA에게 객체를 저장하면 된다.!
객체는 참조를 사용해서 연관된 객체를 조회하고, 테이블은 외래키를 사용해서 조인을 이용해 연관된 테이블을 조회한다.
예로 멤버 객체는 팀 필드로 연관관계를 맺고 멤버 테이블은 팀id 외래키로 연관관계를 맺는다. 즉 객체 모델은 외래키가 필요없고 단지 참고만 있으면 되고 반면 테이블은 외래키만 있으면 된다. 결국 개발자가 중간에서 변환 역할을 해야한다.
sql을 직접 다루면 처음 실행하는 SQL에 따라 객체 그래프를 어디까지 탐색할 수 있는지 정해진다. 이것은 언제 끊어질지 모를 객체 그래프를 함부로 탐색할 수 없기 때문에 너무 큰 제약이다.
객체는 동일성 비교(==)를 사용해 객체 인스턴스의 주소 값을 비교하며 동등성 비교(equals)를 사용해 값을 비교한다.
하이버네이트란 자바 언어를 위한 ORM프레임워크이다. JPA의 구현체로, JPA 인터페이스를 구현하며, 내부적으로 JDBC API를 사용한다.
SQL 반복 작업을 하지 않음으로 생산성이 높아지고 유지보수에 있어 장점이다.
그러나, 직접 SQL을 작성하는 것보다는 성능상 좋지 않으며, 메서드 호출만으로 데이터베이스를 조작하기에는 한계가 있다.
+) 이를 보완하기 위해 JPQL을 지원한다.
주의🚨!
엔티티 매니저 팩토리는 하나만 생성해서 애플리케이션 전체에서 공유
-> persistence.xml의 설정 정보를 읽어서 JPA를 동적시키기 위한 기반 객체를 만들고 JPA 구현체에 따라서 데이터베이스 커넥션 풀도 생성하므로 엔티티 매니저 팩토리를 생성하는 비용은 아주 크다!
엔티티 매니저는 쓰레드간 공유하면 안된다!!(사용하고 버려야한다)
JPA의 모든 데이터 변경은 트랜잭션 안에서 실행
✅ @Entity
이 클래스를 테이블과 매핑한다고 알린다.
✅ @Table
엔티티 클래스에 매핑할 테이블 정보를 알려준다. name속성을 사용해 매핑한다.
✅ @Id
기본키를 매핑한다.
✅ @Column
필드를 컬럼에 매핑하는데, name속성을 사용한다.
Jpa를 잘 이해하지 못하고 사용하면 N+1문제가 발생하는데, 이 문제는 무엇인가?
JPA N+1 문제는 연관 관계가 설정 된 엔티티를 조회할 경우에, 조회된 데이터 개수만큼 연관 관계의 조회 쿼리가 추가로 발생하는 현상입니다. 예를 들어, 블로그 게시글과 댓글이 있는 경우, 게시글을 조회한 후 각 게시글마다 댓글을 조회하기 위해 추가 쿼리가 발생한다면 N + 1 문제가 발생한 것입니다. 댓글 10개가 달린 게시글 1개를 조회하는데 총 11개의 쿼리(게시글 조회 1개 + 각 게시글의 댓글 조회 10개)가 실행됩니다.
findAll 메서드의 글로벌 패치 전략 별 N + 1 문제 상황에 대해서 설명해주세요. 🤓
spring data jpa에서 제공하는 repository의 findAll 메서드입니다!
글로벌 패치 전략을 즉시로딩으로 설정하고 findAll()을 실행하면 N + 1 문제가 발생합니다. 이는 findAll()은 select u from User u라는 JPQL 구문을 생성해서 실행하기 때문입니다. JPQL은 글로벌 패치 전략을 고려하지 않고 쿼리를 실행합니다. 모든 User를 조회하는 쿼리 실행 후, 즉시로딩 설정을 보고 연관관계에 있는 모든 엔티티를 조회하는 쿼리를 실행합니다.
글로벌 패치 전략을 지연 로딩으로 설정하고 findAll()을 실행하면 N + 1 문제가 발생하지 않습니다. 이는 연관관계에 있는 엔티티를 실제 객체 대신에 프록시 객체로 생성하여 주입하기 때문입니다. 하지만 프록시 객체를 사용할 경우에 실제 데이터가 필요하여 조회하는 쿼리가 발생하고 N + 1 문제가 발생할 수 있습니다.
N + 1 문제는 어떻게 해결할 수 있을까요? 🤔
N + 1 문제를 해결하기 위해서는 fetch join, @EntityGraph를 사용해 볼 수 있습니다. fetch join은 연관 관계에 있는 엔티티를 한번에 즉시 로딩하는 구문입니다. @EntityGraph도 비슷한 효과를 만들어내며, 쿼리 메서드에 해당 어노테이션을 추가해 사용할 수 있습니다.
select distinct u
from User u
left join fetch u.posts
@EntityGraph(attributePaths = {"posts"}, type = EntityGraphType.FETCH)
List<User> findAll();
엔티티 매니저에 대해 설명해주세요.
엔티티 매니저에 대해 알기 위해선 영속성 컨텍스트에 대해 알아야 합니다. 영속성 컨텍스트는 엔티티를 영구 저장하는 환경으로 1차 캐싱, 쓰기 지연, 변경 감지를 통해 영속 로직을 효율적으로 할 수 있게 해줍니다. 이러한 효율적인 영속 로직 수행을 위해서 엔티티는 영속성 컨텍스트에 관리되어야 합니다. 이런 작업을 도와주는 것이 바로 엔티티 매니저입니다. 엔티티 매니저는 엔티티의 상태를 변경하고, 영속성 컨텍스트와 상호작용함으로써 영속 로직을 수행하는 역할을 가지고 있습니다.
조금 더 구체적으로 엔티티 매니저의 역할을 설명해 주실 수 있을까요? 🤔
엔티티는 영속성 컨텍스트와 관련하여 4가지 상태(비영속, 영속, 준영속, 삭제)를 가질 수 있는데요. 엔티티 매니저는 persist, merge, remove, close 메서드를 이용하여 엔티티의 상태를 변경할 수 있습니다. 또한, 엔티티 매니저는 영속성 컨텍스트의 1차 캐시로부터 엔티티를 조회할 수 있으며, 쓰기 지연 저장소에 있는 쿼리들을 flush하여 DB와 동기화시킬 수 있습니다. 또한 JPQL이나 Native Query를 이용해 직접 DB로부터 데이터를 불러올 수도 있습니다.