
📃 요약
실무에서는 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="조인컬럼") 해결 방법이 있으나 페이징 처리가 안되거나, 성능이 느려지는 현상이 있어 단점을 고려해서 사용해야함