특정 엔티티의 영속 상태가 변경되었을 때 종속된 엔티티들의 영속 상태가 대상 엔티티를 따라 함께 반영되는 것을 말한다.
JPA에서 테이블 사이의 관계를 맺어주는 방법으로 @OneToOne, @OneToMany, @ManyToOne, @ManyToMany 등의 어노테이션을 사용할 때 cascade 라는 속성을 통해 종속된 엔티티의 영속 상태가 함께 반영되는 시점을 지정할 수 있다.
+) refresh() : 데이터베이스로부터 인스턴스 값을 다시 읽어 오기
부모 엔티티와 연관관계가 끊어진 자식 엔티티를 고아 객체라 하며, JPA에서 부모와 연관 관계가 끊어진 자식 엔티티를 자동으로 삭제하는 고아 객체 제거 기능을 제공한다. (orphanRemoval = true)
참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제한다.
부모 엔티티를 삭제할 때는 CascadeType.REMOVE 와 orphanRemoval = true 모두 동일하게 자식 엔티티도 같이 삭제된다. 하지만 부모 엔티티에서 자식 엔티티를 제거하는 경우, CascadeType.REMOVE는 자식 엔티티가 그대로 남아있고 rphanRemoval = true는 자식 엔티티를 고아 객체로 판단해 제거한다.
데이터베이스 테이블은 외래 키 하나로 양 쪽 테이블에서 조인이 가능하지만, 객체는 참조용 필드가 있는 객체만 다른 객체를 참조하는 것이 가능하다. 븍, 객체의 연관관계가 단방향이어도 테이블에서는 외래키를 통해 양방향 관계이다.
관계형 데이터베이스는 테이블 2개로 다대다 관계를 표현할 수 없다. 따0라서, 보통 다대다 관계를 일대다, 다대일 관계로 풀어내는 연결 테이블을 사용한다.
연결 테이블을 자동으로 처리해주므로 도메인 모델이 단순해지고 편리하다.
하지만!!
연결테이블에 추가 컬럼을 담을 필요가 생길 수 있기 때문에 직접 정의해 주는 것이 좋다. 또한 기본키의 경우 식별 관계보다 비식별 관계가 단순하고 편리하다.
+) 식별관계 : 부모의 기본키를 자식의 기본키+식별키로 사용한다. 비식별관계 : 부모의 기본키는 외래키로만 사용하고 새로운 기본키 정의
일대다 단방향 매핑의 단점은 매핑한 객체가 관리하는 외래 키가 다른 테이블에 있다는 것이다.
-> 본인 테이블에 외래 키가 있으면 엔티티 저장과 연과관계 매핑 처리를 insert 쿼리 한번으로 끝낼수 있지만, 다른 테이블에 있기 때문에 연관관계 매핑 처리를 위한 Update 쿼리 (외래키를 갖고있는 엔티티)를 추가로 실행해야 한다.
따라서, 일대다 단방향 매핑은 성능 문제와 관리 비용이 발생하기 때문에 다대일 양방향 매핑이 효율적이다.
일대일 관계는 주 테이블이나 대상 테이블 둘 중 어느 곳이나 외래 키를 가질 수 있으며 선택해야 한다. 필요할 경우 양방향
보통 주 테이블에 외래 키를 갖는 것이 편리하기 때문에 선호한다.
양방향은 실질적으로 단항향 관계 2개가 이어진 것으로, 외래 키를 비롯한 제어의 권한을 갖는 연관 관계의 주인이 누군지 mappedBy 속성을 사용해서 지정해 줘야 한다. 즉, mappedBy 속성에 연관관계 주인인 엔티티의 참조 필드(외래키)를 지정해 주어야 한다.
+) 연관 관계의 주인은 연관 관계를 갖는 두 객체 사이에서 조회, 저장, 수정, 삭제를 할 수 있지만, 연관 관계의 주인이 아니면 조회만 가능하다.
+) 데이터베이스는 무조건 다(N)쪽이 외래 키를 갖는다.
기본적으로 단방향 매핑으로 하고 나중에 역방향으로 객체 탐색이 꼭 필요하다고 느낄 때 추가하는 것이 좋다.
JPA의 양방향 매핑 관계에서 잘못하면 무한루프가 발생할 가능성이 있다.
이런 상황은 보통 엔티티를 JSON으로 변환하려는 상황에서 발생한다. 예를들어 회원, 팀이 양방향 관계에 놓여있다고 했을 때 회원 엔티티를 JSON으로 변환 한다고 가정한다면, 회원에서는 팀을 팀에서는 회원을 JSON으로 변환시키려는 과정에서 무한 루프가 발생한다.
그래서 보통 dto를 만들고 JSON으로 변환할 데이터만 정의해서 그 dto로 JSON을 만든다.
Java Persistence Query Language의 약자로, DB 테이블이 아니라 엔티티의 객체를 대상으로 검색하는 객체 지향 쿼리이다. JPA는 JpaRepository를 상속한 인터페이스 메소드 이름을 분석해서 JPQL로 변환하고, 이를 가지고 SQL을 만들어서 DB에 SQL을 실행하는 과정을 거친다.
jpa의 entity 조회시 Query 가 발생하고 내부에 존재하는 다른 연관관계에 접근할 때 조회화는 데이터 개수 N만큼 쿼리가 발생하는 현상
이론적으로 즉시로딩은 조인을 사용해서 한 번의 SQL로 테이블 정보를 함께 조회하지만 JPQL을 실행할 때 문제가 발생한다.
예를 들어 특정 회원 정보를 조회할 때, 회원 엔티티 조회 커리가 실행되고(1) 연관된 주문 엔티티에서 해당 회원을 참조하는 주문 데이터 N개에 대한 N개의 SQL이 실행된다.
초기에는 해당 엔티티만 조회하는 쿼리가 한번 실행되지만, 객체 초기화 할 때 초기화하는 수 만큼 N+1문제가 발생한다.
가장 일반적인 방법으로 JPQL에서 성능 최적화를 위해 제공하는 기능이다.
select distinct m from Member m join fetch m.orders
패치 조인은 inner join이기 때문에 일대다 관계에서 일인 테이블을 기준으로 데이터를 조회하면 카티션 곱이 발생해 데이터가 중복되므로 distinct를 함께 사용해 줘야 한다.
페이징 처리 문제
JPA는 Pageable 인터페이스를 통해 쉬운 페이징 처리 기능을 제공한다. 하지만 N+1 문제 회피를 위해 사용하는 fetch 조인과 함께 사용한다면 문제가 될 수 있다.
@EntityGraph 애너테이션에 함께 조인할 엔티티 필드를 지정한다. 쿼리를 별도로 만들지 않아도 되기 때문에 편하다.
@EntityGraph(attributePaths = {"team"})
List<Member> findAll();
연관된 엔티티를 조회할 때 지정한 size 만큼 SQL의 in절을 사용해서 조회한다. 예를들어, size가 5이고 전체 데이터가 10개라면 2번의 SQL이 실행된다,
@BatchSize(size = 5)
@OneToMany(mappedBy = "member", fetch = FetchType.EAGER)
private List<Order> orders = new ArrayList<Order>();
JPA에서는 read=true 속성이 설정되면 영속성 컨텍스트 flush가 발생하지 않습니다. Flush는 영속성 컨텐스트의 변경내용을 데이터 베이스와 동기화하는 작업을 말합니다.