🌟즉시로딩( EAGER )은 예측이 어렵고, 어떤 SQL이 실행될지 추적하기 어렵다. 특히 JPQL을 실행할 때 N+1 문제가 자주 발생한다.
🌟실무에서 모든 연관관계는 지연로딩( LAZY )으로 설정해야 한다.
N+1문제
연관 관계에서 발생하는 이슈로 연관 관계가 설정된 엔티티를 조회할 경우에 조회된 데이터 갯수(n) 만큼 연관관계의 조회 쿼리가 추가로 발생하여 데이터를 읽어오게 된다. 이를 N+1 문제라고 한다.
N+1문제는 왜 발생하는 것일까?
jpaRepository에 정의한 인터페이스 메서드를 실행하면 JPA는 메서드 이름을 분석해서 JPQL을 생성하여 실행하게 된다. JPQL은 SQL을 추상화한 객체지향 쿼리 언어로서 특정 SQL에 종속되지 않고 엔티티 객체와 필드 이름을 가지고 쿼리를 한다. 그렇기 때문에 JPQL은 findAll()이란 메소드를 수행하였을 때 해당 엔티티를 조회하는 select * from Owner 쿼리만 실행하게 되는것이다. JPQL 입장에서는 연관관계 데이터를 무시하고 해당 엔티티 기준으로 쿼리를 조회하기 때문이다. 그렇기 때문에 연관된 엔티티 데이터가 필요한 경우, FetchType으로 지정한 시점에 조회를 별도로 호출하게 된다.
🌟컬렉션은 필드에서 바로 초기화 하는 것이 안전하다.
스프링 부트에서 하이버네이트 기본 매핑 전략을 변경해서 실제 테이블 필드명은 다름
하이버 네이트 기존 구현: 엔티티의 필드명을 그대로 테이블의 컬럼명으로 사용( SpringPhysicalNamingStrategy
)
스프링 부트 신규 설정 (엔티티(필드) 테이블(컬럼))
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "dtype")
@Getter @Setter
public abstract class Item {
@Id
@GeneratedValue
@Column(name = "item_id")
private Long id;
private String name;
private int price;
private int stockQuantity;
@ManyToMany(mappedBy = "items")
private List<Category> categories = new ArrayList<>();
//==비즈니스 로직==//
/**
* stock 증가
*/
public void addStock(int quantity) {
this.stockQuantity += quantity;
}
/**
* stock 감소
*/
public void removeStock(int quantity) {
int restStock = this.stockQuantity - quantity;
if (restStock < 0) {
throw new NotEnoughStockException("need more stock");
}
this.stockQuantity = restStock;
}
}
도메인 모델 패턴(Domain Model)
엔티티가 비즈니스 로직을 가지고 객체 지향의 특성을 적극 활용하는 것을 도메인 모델 패턴이라 한다.
객체 지향 분석 설계에 기반해서 구현하고자 하는 도메인(비즈니스 영역)의 모델을 생성하는 패턴이다.
쉽게 말해 Domain 부분에서 비즈니스 로직을 이용해 개발을 진행하는 것을 얘기한다.
트랜잭션 스크립트 패턴(Transaction Script)
엔티티에는 비즈니스 로직이 거의 없고 서비스 계층에서 대부분의 비즈니스 로직을 처리하는 것을 트랜잭션 스크립트 패턴이라 한다.
쉽게 말해 Domain부분이 아닌 Service 부분에서 비즈니스 로직을 이용해 개발을 진행하는 것을 얘기한다.
어떤 패턴이 더 좋다 나쁘다는 없다. 한 프로젝트안에서도 양립할 수 있다.