+ 00 00 0000

Have any Questions?

06_Simple Coding – Backend – JPA 영속성_매핑 – 기본 요약

06_Simple Coding – Backend – JPA 영속성_매핑 – 기본 요약

📃 요약

실무에서는 MVC 디자인 패턴 속해서 스프링부트를 사용해 코딩을 진행함. 파일관리 시 파일을 용도에 맞게 정리하듯이 MVC 디자인 패턴도 패키지 및 클래스를 용도에 맞게 분류해서 코딩을 진행함

요소 기술 :

– Basic : java & spring boot

📃 기술 구현

스펙 :

- intellij
- java
- spring boot

📃 JPA 영속성 컨테이너 :

- JPA 는 쿼리가 실행되면 데이터베이스에서 결과를 가져와서 영속성 컨테이너라는 메모리 공간에 저장해둠
- 위의 공간을 1차 캐쉬라고 하고 , 같은 쿼리를 요청할때 데이터베이스로 가지않고 1차 캐쉬에서 조회해서 성능을 향상시킴

📃 JPA 더티 채킹 :

    - JPA 는 수정이 일어나서 1차 캐쉬의 값이 변경되면 DB 에 자동으로 수정하는 update 문을 실행해서 1차 캐쉬와 DB 의 값을 일치시킴
    - 개발자가 직접 save() 함수를 실행하지 않고 setter 로 엔티티의 값을 수정하면 더티 채킹기능으로 자동으로 update 문이 1) 의 이유로 실행됨
    - 더티 채킹 기능을 사용하기 위해서는 서비스 함수위에 @Transactional 어노테이션을 붙여야 사용할 수 있음
예 :
    @Transactional
    public List<Dept> save(Dept dept) {

        // 객체 수정 : 더티 채킹으로 update 쿼리문 실행됨
        // 더티 채킹 사용으로 save() 함수 생략됨
        dept.setDname("Develop"); 

        return list;
    }

📃 관계 매핑 :

- JPA 는 테이블 중심 설계에서 객체 중심 설계로 바꾸어 주는 기술임. 이것을 ORM 이라고 함
- 기존의 DB 설계시 부모 - 자식 테이블 간의 관계를 객체들끼리 서로 관계를 맺어주는 기능이 있음
- 프로젝트 설계시 JPA 를 이용하되 객체 관계를 맺을지 아니면 객체 관계를 생략하고 DB 테이블 끼리만 관계를 설정하고 객체 관계는 생략할지 전략을 세운 후 진행함
- 객체관계 어노테이션은 대표적으로 @ManyToOne , @OneToMany, @OneToOne 이 있으며 관계 설정은 단방향과 양방향이 있음
    1) 단방향 : A 와 B 클래스 관계 설정시 B만 관계 어노테이션을 사용하는 것
    2) 양방향 : A 와 B 클래스 관계 설정시 A, B 모두 관계 어노테이션을 사용하는 것

즉시로딩(Eager) vs 지연로딩(Lazy) :

1) 즉시로딩(Eager) : 엔티티를 조회할때 해당 엔티티와 매핑된 엔티티도 한번에 조회하는 것
    - 불필요한 조인이 많이 일어나고, N + 1 문제가 발생할 수도 있음
        1) N + 1 문제 : 1st 테이블에 대한 데이터 하나당 n 개의 개별 쿼리가 많들어져 대폭 속도 저하가 발생할 수 있음
2) 지연로딩(Lazy) : 엔티티를 조회할때 매핑된 엔티티는 조회하지 않고, 매핑된 엔티티 속성이 getter 함수에 의해 실행될때 조회됨
    - 실무에서 주로 지연로딩을 사용함

단방향 @ManyToOne :

- N : 1 관계에서 N인 클래스에 사용 
- 주로 FK(외래키) 가 만들어질 대상 클래스 임
- 주로 단방향으로 관계 설정을 하고 필요시 양방향으로 전환함

  • 단방향 @ManyToOne



    사용법 :
    @Entity
    @Table(name = "TB_EMPLOYEE")
    public class Employee extends BaseTimeEntity {
    ...

    @ManyToOne
    @JoinColumn(name="dno")
    @JsonBackReference
    private Department department;
    }

예 :
@Entity
@Table(name = “TB_EMPLOYEE”)
public class Employee extends BaseTimeEntity {

// n : 1 관계 설정
@ManyToOne
@JoinColumn(name=”dno”)
@JsonBackReference
private Department department;
}

### 양방향 @ManyToOne, @OneToMany :
    - @ManyToOne : N : 1 관계에서 N인 클래스에 사용 (FK 가 생성될 객체)
    - @OneToMany : N : 1 관계에서 1인 클래스에 사용 
        1) 매핑 속성(필드)은 데이터 개수가 N 이므로 중복을 제거하는 Set 으로 설정
        2) mappedBy = "@ManyToOne속성명" : 수정/삭제/추가가 가능한 엔티티의 속성(필드)를 지정
            - 연관관계 주인 : 수정/삭제/추가가 가능한 엔티티를 의미
            - 양방향관계에서는 양쪽 모두 수정/삭제/추가가 가능하므로 JPA 에서 관리 효율성의 목적으로 한쪽에만 수정/삭제/추가 가 가능하게 설정함
            - 이때 주로 N 클래스에(FK 가 생성될 엔티티) 수정/삭제/추가 권한을 부여함(연관 관계 주인이됨)
    - 양방향 관계 설정 시 N + 1 문제가 발생할 가능성이 있음
        1) 1st 테이블에 대한 데이터 하나당 n 개의 개별 쿼리가 많들어져 대폭 속도 저하가 발생할 수 있음
        2) 해결책은 아래와 같음
            (1) @ManyToOne(fetch = FetchType.LAZY) 사용 : 지연로딩 사용
                - 기본은 즉시로딩 옵션이며 , 지연로딩으로 설정
                - 지연로딩으로 설정하더라도 매핑된 속성을 조회하면 N + 1 문제가 발생할 수 있음
                - 이때는 아래 2번 또는 3번으로 해결가능함
            (2) application.properies 에 아래 속성 추가 :
                - spring.jpa.properties.hibernate.default_batch_fetch_size=1000
            (3) fetch join 키워드로 해결가능하나 페이징이 안된다는 단점이 있음
    - 양방향 관계 설정 시 순환 참조 문제가 발생할 가능성이 있음
        1) 양방향 관계 설정 시 A <-> B 가 서로 참조하는 속성이 있음, 그 속성들이 결과를 출력할때 계속 서로 결과를 출력해서 무한히 출력되는 현상이 발생될 수 있음
        2) 해결책은 서로 참조하는 속성을 출력하지 않도록 설정해야함, 아래가 출력을 막는 어노테이션임 
            - @JsonManagedReference, @JsonBackReference 
            - @ManyToOne 속성(필드) : @JsonManagedReference 붙임
            - @OneToMany 속성(필드) : @JsonBackReference 붙임

- 양방향 @ManyToOne
```java 
사용법 : 
@Entity
@Table(name = "테이블명")
public class 클래스명 extends 공통클래스 {
    ...

    @ManyToOne
    @JoinColumn(name="조인될컬럼명")
    @JsonBackReference
    private 부모클래스 부모객체변수;     // 자식클래스의연관관계속성
}    

예 :
@Entity
@Table(name = "TB_EMPLOYEE")
public class Employee extends BaseTimeEntity {
    ...

//  n : 1 관계 설정
    @ManyToOne
    @JoinColumn(name="dno")
    @JsonBackReference               // 순환참조로 인한 에러 해결 
    private Department department;   // 자식클래스의연관관계속성
}

  • 양방향 @OneToMany



    사용법 :
    @Entity
    @Table(name = "부모테이블명")
    public class 부모클래스명 extends 공통클래스 {
    ...

    @OneToMany(mappedBy = "자식클래스의연관관계속성")
    @JsonManagedReference
    private Set<Employee> employee = new HashSet<>(); // 1:n(사원)
    }

예 :
@Entity
@Table(name = “TB_DEPARTMENT”)
public class Department extends BaseTimeEntity {

@OneToMany(mappedBy = "department")
@JsonManagedReference                             // 순환참조로 인한 에러 해결 
private Set<Employee> employee = new HashSet<>(); // 1:n(사원)

}

### 연관관계 시 에러 해결 : 순환참조, N+1 문제

#### 순환참조
1) 순환참조 : 재귀함수처럼 부모 - 자식 관계에서 서로 양방향 참조가 되어 있다면 계속 순환해서 참조하는 현상,

예) Department – Employee 양방향 매핑 관계에서
(1) Department 의 employee 속성필드 접근하면 Employee 모든 속성(필드)들이 출력됨
(2) Employee 의 모든 속성(필드)을 출력하는 도중 department 속성(필드)를 만나면
(3) 다시 Department 모든 속성을 재 출력하게 됨
(4) 그럼 다시 Department 의 employee 속성필드 접근하게 되고 이것이 무한히 순환하면서 반복되는 현상

2) 해결 : 
(1) 부모클래스 : 연관관계 속성(필드)에 @JsonManagedReference 추가 
(2) 자식클래스 : @JsonBackReference 추가        
(3) 위의 해결방법을 추천하고 기타 다른 해결법등도 전해지고 있음
#### N+1 문제
1) N+1 : 부모 - 자식 관계에서 즉시로딩 방법일경우 조회시 연관된 클래스까지 모두 조회하기 때문에 생기는 문제
부모 의 각 데이터 당 자식을 조회하는 select 문이 각각 실행되는 현상, 굉장히 느린 성능으로 서비스를 다운시킬 수 있음
예) Department - Employee 연관관계에서
   부서테이블 조회                          : select * from tb_department;
   부서테이블의 부서번호(10 ~ 40) 각각에 대해 다시 조회 : 
            10에 대해 각각 자식 쿼리가 실행됨   : select * from tb_employee where dno = 10;
        또   20에 대해 각각 자식 쿼리가 실행됨  : select * from tb_employee where dno = 20;
        또   30에 대해 각각 자식 쿼리가 실행됨  : select * from tb_employee where dno = 30;
        또   40에 대해 각각 자식 쿼리가 실행됨  : select * from tb_employee where dno = 40;
   => 성능 극도로 저하
2) 해결 :
(1) 즉시로딩 -> 지연로딩 수정 : 실무에서는 무조건 지연로딩으로 사용
(2) 지연로딩으로 하더라도 연관관계 클래스의 속성(필드)를 출력할때 다시 N+1 문제 발생
    - 지연로딩은 연관관계 클래스를 바로 조회않하고 그 클래스를 직접 조회할 일이 생길때 그때 조회됨 : 이때 N+1 문제 다시 발생
    - 해결 : (추천) application.properties 설정으로 N+1 문제 해결(BatchSize) : 여러개 sql 문 -> in 사용 sql 문으로 변경됨
         - 개수는 대략 부모테이블 건수만큼
        예) spring.jpa.properties.hibernate.default_batch_fetch_size=1000 
    - 그외 fetch join , @EntityGraph(attributePaths="조인컬럼") 해결 방법이 있으나 페이징 처리가 안되거나,
      성능이 느려지는 현상이 있어 단점을 고려해서 사용해야함

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다