프록시 객체
ㅇ
지연 로딩
데이터베이스에서 필요한 데이터만을 필요 시점에 로딩함으로써 애플리케이션의 성능을 최적화하는 데 큰 기여한다.
지연 로딩의 핵심 개념은 객체가 참조하고 있는 다른 객체나 컬렉션의 데이터를 처음부터 메모리에 로딩하지 않고, 해당 데이터에 실제로 접근하려는 시점에 로딩을 수행하는 것
→ 즉 커밋할 때 한번에 로드할 수 있도록 하는 것
지연 로딩의 동작 방식은 **프록시 패턴(Proxy Pattern)**을 기반으로 함
프록시 객체는 엔티티의 메타데이터와 식발자를 보유하여 엔티티 접근 시도가 있을 때 DB세션이 열려 있는지 확인하고 데이터베이스 쿼리를 실행하여 데이터를 로드한다. 데이터베이스 세션이 닫힌 상태에서 프록시 객체에 접근하면 LazyInitializationException 발생.
지연로딩 설정은 fetch에 값을 FetchType.LAZY로 설정하면 된다.
@Entity
public class BaseBallPlayer {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private BaseBallTeam team;
}
즉시로딩
특정 객체와 그 객체가 연관된 다른 객체들을 한꺼번에 로드하여 애플리케이션의 성능과 사용성을 향상시키는 방법이다.
예를 들어, A 객체가 B 객체를 참조하고 있을 때, 즉시 로딩이 설정되어 있다면 A 객체를 로드하는 동시에 B 객체도 함께 로드된다.
단점.
불필요한 데이터를 메모리에 로드하게 되어 메모리 사용량이 증가할 수 있다.
예를 들어, 특정 엔티티가 다수의 연관된 엔티티를 가지고 있을 때, 모든 연관 엔티티를 즉시 로딩하게 되면 시스템의 메모리 리소스를 과도하게 사용하게 된다는 단점이 있다.
즉시로딩 설정 FetchType.EAGER
@Entity
public class BaseBallPlayer {
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "team_id")
private BaseBallTeam team;
}
영속성 전이
부모 엔티티에 대해 수행된 특정 작업이 연관된 자식 엔티티에도 자동으로 전파되도록 하는 것
예를 들어, 부모 객체를 데이터베이스에 저장할 때, 연관된 자식 객체들도 자동으로 저장되도록 설정할 수 있다.
Cascade 데이터베이스에서 cascade는 외래 키 제약조건(Foreign Key constraint)이 참조하는 기본키가 삭제되거나 업데이트 되었을 때 그 부모 레코드와 연관된 자식 테이블의 레코드도 자동으로 삭제되거나 업데이트되는 방식
연관관계에서 CasCade를 설정은 다음과 같이 정의된다.
@OneToMany, @ManyToOne, @OneToOne, @ManyToMany와 같은 관계 매핑 어노테이션에 cascade = CascadeType.XXX 형식으로 설정.
CascadeType 에는 ALL, PERSIST, MERGE, REMOVE, REFRESH, DETACH 가 존재한다.
- PERSIST CascadeType.PERSIST는 부모 객체를 영속화할 때, 연관된 자식 객체들도 함께 영속화하도록 설정. 예시 : 새로운 A 객체를 생성하면서 그와 연관된 B 객체들을 추가하고, A 객체를 데이터베이스에 저장할 때, B 객체들도 자동으로 데이터베이스에 저장하도록 한다.
- MERGE 부모 객체의 상태를 병합할 때, 연관된 자식 객체들도 함께 병합된다. 예시 : A 객체의 정보를 업데이트할 때, 그와 연관된 B 객체도 함께 업데이트 된다.
- REMOVE 부모 객체가 삭제될 때, 연관된 자식 객체들도 함께 삭제된다. 예시 : 특정 A 객체를 삭제할 때, 그와 연관된 모든 B 객체들도 자동으로 삭제되도록 설정하여 부모-자식 관계에서 불필요한 데이터가 남지 않도록 한다.
- REFRESH 부모 객체를 새로고침할 때, 연관된 자식 객체들도 함께 새로고침 된다. 데이터베이스에서 최신 데이터를 가져와 엔티티 상태를 동기화할 때 유용하다.
- DETACH 부모 객체가 비영속화될 때, 연관된 자식 객체들도 함께 비영속화된다. 세션의 관리를 효율적으로 처리할 수 있도록 도와준다. 예시 : 특정 A 객체를 detach할 때, 그와 연관된 B 객체도 함께 detach되어 세션 관리가 간소화된다.
- ALL 앞서 언급한 모든 전이 작업을 한꺼번에 적용하는 설정 부모 객체에 대해 수행된 모든 작업이 자식 객체에도 동일하게 적용되도록 설정하는 것이며, 이를 통해 부모와 자식 객체 간의 상태를 항상 동기화된 상태로 유지할 수 있다.
N+1 문제
ORM을 사용하여 객체와 DB 테이블 간 매핑이 이루어지며, 이를 통해 개발자는 DB의 세부 사항을 신경쓰지 않고 객체지향적으로 코드를 작성할 수 있다.
하지만 ORM으로 생성된 코드는 자동화된 커리이므로 성능 최적화면에서 복잡하고 불필요한 코드가 발생할 수 있다 이 문제 중 가장 대표적인 것이 N+1문제이다.
@Getter
@Setter
@Entity
public class Board {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long boardId;
@Column(nullable = false, length = 50)
private String name;
@OneToMany(mappedBy = "board", fetch = FetchType.LAZY)
private List<Post> postList = new ArrayList<>();
}
@Getter
@Entity
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long postId;
private String title;
private String content;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "board_id")
private Board board;
}
위의 예제와 같이 1개의 게시판에 여러개의 글이 있을 수 있다.
하나의 게시판에 10개의 글이 있다면 1개의 게시판을 조회하면 10개의 글을 조회하는 쿼리가 발생되어 성능저하가 발생된다. 즉 총 부모+자식의 쿼리수 1+10 = 11개의 쿼리가 발생하여 문제가 발생하는 것이 N+1 문제이다.
fetch join
fetch join은 N+1문제를 해소할 수 있는 방법으로 사용된다. fetch join은 연관된 엔티티를 한 번의 쿼리로 모두 조회한다.
jpa에서 사용법은 @OneToMany 또는 @ManyToOne 관계에서 fetch = FetchType.EAGER를 설정하여 즉시 로딩을 사용 하는 것이다.
@OneToMany(fetch = FetchType.EAGER)
private List<Post> posts;
하지만 fetch join또한 데이터의 양이 많아질 경우 , 하나의 쿼리에서 너무 많은 데이터를 가져와 성능이 저하 될 수 있다.
따라서 fetch join외에도 Batch Size와 같은 수단을 통해서 성능 튜닝을 유연히 수행할 수 있어야 한다.
배치 쿼리 (Batch Query): N+1 문제를 해결하기 위한 또 다른 방법은 배치 쿼리.
예를 들어, 여러 부모 엔티티에 대해 한 번에 자식 데이터를 가져오는 방식 JPA에서는 @Query 또는 @BatchSize를 사용하여 배치 처리를 할 수 있다.
'Spring' 카테고리의 다른 글
| OAuth2.0 google, kakao, naver (1) | 2024.12.02 |
|---|---|
| [오르미 백엔드7기] (Spring Security , Cors,CSRF) (1) | 2024.11.29 |
| [오르미 백엔드 7기] (Spring Entity 개념, 연관관계, 공통속성정의) (0) | 2024.11.25 |
| [오르미 백엔드7기] (Spring JPA 기본,ORM이란? , JDBC) (0) | 2024.11.22 |
| [오르미 백엔드 7기] (Spring Controller, thymeleaf 실습) (1) | 2024.11.19 |