inblog logo
|
송승현의 블로그
    스프링부트

    [스프링부트]JPA-연관관계매핑

    송송승현's avatar
    송송승현
    Nov 29, 2024
    [스프링부트]JPA-연관관계매핑
    Contents
    일대일(One-to-One)관계일대다(One-to-Many)관계다대일(Many-To-One)관계다대다(Many-To-Many)관계고려사항주요 속성LAZY 전략EAGER 전략
    💡
    객체 모델과 관계형 데이터베이스 모델 간의 불일치를 해결하면서 효율적인 데이터 관리를 가능하게 함

    일대일(One-to-One)관계

    • 하나의 엔티티가 다른 엔티티와 1:1로 매핑되는 관계
    • @OneToOne 어노테이션을 사용하여 정의
    • 예제
      • @Entity public class User { @Id @GeneratedValue private Long id; @OneToOne private Profile profile; }

    일대다(One-to-Many)관계

    • 하나의 엔티티가 여러 엔티티와 열결되는 관계
    • 단방향 관계 설정이 어렵고, 양방향 매핑을 많이 사용
    • @OneToMany 어노테이션을 사용해 정의
    • 예제
      • @Entity public class User { @Id @GeneratedValue private Long id; @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) private List<Post> posts = new ArrayList<>(); } @Entity public class Post { @Id @GeneratedValue private Long id; @ManyToOne @JoinColumn(name = "user_id") private User user; }

    다대일(Many-To-One)관계

    • 여러 엔티티가 하나의 엔티티와 열결되는 관계
    • 외래 키가 포함된 테이블이 연관관계의 주인 역할을 함
    • @ManyToOne 어노테이션을 사용해 정의
    • 예제
      • @Entity public class Post { @Id @GeneratedValue private Long id; @ManyToOne @JoinColumn(name = "author_id") private Author author; }

    다대다(Many-To-Many)관계

    • 여러 엔티티가 서로 다수와 연결되는 관계
    • 중간 매개 테이블을 정의하여 다대다 관계를 사용하는 것이 일반적
    • @ManyToMany 어노테이션을 사용해 정의
    • 예제
      • @Entity public class Student { @Id @GeneratedValue private Long id; @ManyToMany @JoinTable(name = "student_course", joinColumns = @JoinColumn(name = "student_id"), inverseJoinColumns = @JoinColumn(name = "course_id")) private List<Course> courses = new ArrayList<>(); }

    고려사항

    • 연관관계의 주인(owner)
      • 관계의 주인이 되는 엔티티가 외래 키를 관리하며 데이터베이스와 직접적인 매핑 역할을 함
      • mappedBy 속성은 주인이 아님을 명시
    • 양방향과 단방향
      • 양방향 관계 : 엔티티 간의 참조를 서로 설정
      • 단방향 관계 : 하나의 엔티티만 다른 엔티티를 참조

    주요 속성

    • mappedBy : 연관관계의 주인이 아닌 엔티티에서 사용하여 양방향 관계 정의
    • cascade : 연관된 엔티티를 함께 저장, 삭제
    • fetch : 로딩 방벙 정의
      • EAGER : 즉시 로딩
        • 엔티티를 조회할 때 연관된 엔티티를 함께 가져옴
        • 연관 엔티티가 많을 경우 성능 저하 발생
      • LAZY : 지연 로딩
        • 연관된 엔티티를 실제로 사용할 때 데이터를 가져옴
        • 성능 최적화를 위해 기본적으로 사용

    LAZY 전략

    설명

    • 연관된 데이터를 실제로 사용할 때 로딩하는 방식
    • 필요하지 않는 데이터를 미리 로딩하지 않으므로 초기 로딩 시 성능 최적화 가능
    • 연관된 데이터를 사용하지 않을 겨우 데이터베이스 접근을 최소화
    • 메모리와 네트워크 자원을 절약하고 , 엔티티가 가벼워짐
    • 실제 연관된 데이터를 참조하려는 시점에서 데이터베이스에서 쿼리를 실행함
    • 연관 데이터를 사용하는 시점에 추가 쿼리가 발생, 영속성 컨텍스트가 닫혀 있으면 LazyInitializationException 발생 가능
    • 보통 DB세션을 뷰 레이어까지 열어두게 되면 발생가능성이 높음(OSIV)
    • 예제
      • @Entity public class User { @Id @GeneratedValue private Long id; @OneToMany(mappedBy = "user", fetch = FetchType.LAZY) private List<Post> posts = new ArrayList<>(); }
        User user = em.find(User.class, userId); // Post는 로딩되지 않음 List<Post> posts = user.getPosts(); // 여기서 추가 데이터베이스 쿼리가 실행됨

    OSIV

    • JPA의 영속성 컨텍스트는 데이터베이스 연결을 유지하며 엔티티를 관리
    • OSIV는 이 영속성 컨텍스트를 트랜젝션이 끝난 후에도 뷰 레이어까지 확장하여 유지
      • OSIV 활성화 상태
        • 데이터베이스 세션이 Controller와 View까지 열려 있음
        • 이로 인해 LAZY 로딩이 데이터 View에서 접근이 가능
        • 뷰에서 지연로딩을 남발하면 다수의 추가 쿼리 문제가 발생
        • 영속성 컨텍스트가 뷰까지 열려 있으면 데이터베이스 커넥션이 길게 유지 되어 자원소모가 커짐
        • 대규모 트래픽 처리 시 병목이 발생
        • 트랜잭션 범위를 벗어난 데이터 로딩으로 인해 설계가 불명확해질 가능성이 있음
      • OSIV 비활성화 상태
        • 데이터베이스 세션이 Service에서 트랜잭션이 종료되면 닫힘
        • 트랜잭션 외부에서 LAZY 로딩 데이터를 접근하려고 하면 LazyInitializationException 발생

    지연로딩의 동작 방식

    • JPA에서 지연 로딩은 연관된 엔티티를 실제로 액세스할 때까지 로딩을 미룬다는 의미
    • 지연 로딩 필드는 프록시 객체로 초기화, 해당 필드에 접근할 때 데이터베이스 쿼리가 실행
    • 영속성 컨텍스트(EntityManager)가 닫힌 상태에서 지연 로딩을 호출하면 데이터베이스 접근이 불가능해져 LazyInitializationException이 발생

    직렬화와의 충돌

    • 지연 로딩된 필드는 Hibernate 프록시 객체로 존재
    • 이 프록시 객체는 실제 데이터를 로드하기 위해 영속성 컨텍스트(EntityManager)를 필요
    • 그러나 영속성 컨텍스트는 보통 직렬화 시점에 닫혀 있으므로 데이터 로딩이 불가능
    • LazyInitializationException 또는 직렬화 오류가 발생

    EAGER 전략

    • 연관된 데이터를 즉시 로딩하는 방식
      • 엔티티가 조회될 때, 연관된 엔티티를 함께 로딩
      • 연관된 데이터를 자주 사용하는 경우 유용
    • 데이터베이스에서 JOIN 쿼리를 사용하여 한 번에 가져오거나 별도의 추가 쿼리를 실행
    • 연관 데이터를 함께 가져오기 때문에 추가적인 데이터베이스 쿼리가 줄어듦
    • 코드가 간결하고 예측 가능
    • 필요 없는 데이터를 미리 로딩하여 메모리 및 성능 낭비 가능
    • 연관 데이터가 많아질 경우, 쿼리의 복잡도가 증가하거나 대량의 데이터를 메모리에 로드하게 됨
    • 예제
      • @Entity public class User { @Id @GeneratedValue private Long id; @OneToOne(fetch = FetchType.EAGER) private Profile profile; }
        User user = em.find(User.class, userId); // Profile이 함께 로딩됨
    Share article
    Contents
    일대일(One-to-One)관계일대다(One-to-Many)관계다대일(Many-To-One)관계다대다(Many-To-Many)관계고려사항주요 속성LAZY 전략EAGER 전략

    송승현의 블로그

    RSS·Powered by Inblog