TIL - 2022

TIL 20220723 (@DiscriminatorValue / @DiscriminatorColumn, @Enumerated의 타입, N+1 문제,CSRF token missing or incorrect,JSON 서버 구축 )

바랄 희 2022. 7. 25. 02:32

@DiscriminatorValue / @DiscriminatorColumn

 

@DiscriminatorValue 부모가 자식을 구분할 때 자신이 어떤 값을 갖는지 지정하는 역할

 

@DiscriminatorColumn 어노테이션은 어떤 자식인지 구분하는 역할을 하는 칼럼

 

예를 들어 부모가 item, 자식이 album, cloth, cosmetic 이라고 가정한다면,

 

item 에게는 DiscriminatorColumn 이 category 일 수 있고,

DiscriminatorValue 는 album, cloth, cosmetic 일 수 있는 것이다. 

즉 item table 내에서 자식을 구분하는 컬럼을 두고, 자식들이 해당 컬럼에서 뭐라고 표시될 것인지 결정하는 것이다.

 

@Enumerated의 타입

@Enumerated(EnumType.ORDINAL) 이 아닌 @Enumerated(EnumType.STRING)을 써야 한다. 

 

중간에 이넘타입 안에 다른 상태가 생기게 될 경우 순서가 바뀌기 때문에 꼬일 가능성이 커서

 

ORDINAL 은 enum 타입 안에 선언된 순서대로 값을 매겨 정수를 저장하는 반면, STRING 은 문자열 값 그대로 저장한다는 차이가 있다.

 

N+1 문제

계속 듣지만.. 제대로 이해하지 않고 넘어가는 이 문제에 대해서 이번에는 이해해보려고 한다.

 

N+1 문제란,

연관 관계가 설정된 엔티티를 조회할 경우에 조회된 데이터 갯수(n) 만큼 연관관계의 조회 쿼리가 추가로 발생하여 데이터를 읽어오는 현상이다. 예를 들어서, team 과 member 라는 테이블이 있고 (team 하나에 여러 member 가 속할 수 있다고 가정) team 하나를 조회했을 때 team 엔티티의 개수만큼 연관관계를 조회하는 쿼리가 생성되는 것이다. 이런 경우에는 DB가 죽어버릴 수 있다는 우려가 있다.

 

@Entity
public class Team {
    @Id
    @GeneratedValue
    private long id;
    private String name;

    @OneToMany(fetch = FetchType.EAGER) 혹은 @OneToMany(fetch = FetchType.LAZY)
    private List<User> users = new ArrayList<>();
}

 

fetch 전략이란 특정 엔티티를 조회할 때 연관관계에 있는 다른 엔티티를 언제 불러오느냐에 대한 전략이다.

당연하게도 EAGER 은 즉시, LAZY는 연관관계에 있는 엔티티를 사용하려고 하는 시점에 다른 엔티티를 불러온다. 

 

LAZY를 사용하면 항상 N+1 문제가 발생하지 않을까?

답은 아니다

N+1 문제가 덜 발생하는 것 뿐이다. LAZY인 경우에는 연관관계에 있는 엔티티를 사용하지 않으면 N+1 문제가 발생할 일이 없기 때문이다. 

 

fetch 전략이 즉시 로딩인 경우에는


1. findAll()을 한 순간 select t from Team t 이라는 JPQL 구문이 생성되고 해당 구문을 분석한 select * from team 이라는 SQL이 생성되어 실행된다.
2. DB의 결과를 받아 team 엔티티의 인스턴스들을 생성한다.
3. team과 연관되어 있는 user 도 로딩을 해야 한다.
4. 영속성 컨텍스트에서 연관된 user가 있는지 확인한다.
5. 영속성 컨텍스트에 없다면 2에서 만들어진 team 인스턴스들 개수에 맞게 select * from user where team_id = ? 이라는 SQL 구문이 생성된다. ( N+1 발생 )

fetch 전략이 지연 로딩인 경우에는


1. findAll()을 한 순간 select t from Team t 이라는 JPQL 구문이 생성되고 해당 구문을 분석한 select * from team 이라는 SQL이 생성되어 실행된다.
2. DB의 결과를 받아 team 엔티티의 인스턴스들을 생성한다.
3. 코드 중에서 team 의 user 객체를 사용하려고 하는 시점에 영속성 컨텍스트에서 연관된 user가 있는지 확인한다
4. 영속성 컨텍스트에 없다면 2에서 만들어진 team 인스턴스들 개수에 맞게 select * from user where team_id = ? 이라는 SQL 구문이 생성된다. ( N+1 발생 )

 

이를 해결할 수 있는 방법에는 두 가지가 있다.

 

1. fetch join

처음에 엔티티를 조회할 때부터 연관된 엔티티까지 조회하는 방법이다.

별도의 메소드와 @Query 어노테이션을 통해서"join fetch 엔티티.연관관계_엔티티" 구문을 만들어 주면 된다.

public interface TeamRepository extends JpaRepository<Team, Long> {
    @Query("select t from Team t join fetch t.users")
    List<Team> findAllFetchJoin();
}

 

현재 team 엔티티를 조회할 때 t.users 즉 연관된 엔티티인 team의 users 를 조회하는 것으로 쿼리문을 생성한 것이다.

이후 findAllI() 이 아닌 findAllFetchJoin() 을 사용하면 되는 것이다. 

 

2. Batch Size

사실상 이는 N+1의 완전 해결이라기보다는 N 번 일어날 상황을 1번으로 줄여주는 것이다.

왜냐하면, select * from user where team_id = ? 라는 쿼리문이 select * from user where team_id = (?,?,?) 이런 식으로 바뀌게 되기 때문이다. 즉, team 엔티티 개수만큼이 아니라 한번 더 쿼리문이 생성되는 것이다.

 

@BatchSize(size = 10)
class Team {
../}

 

BatchSize 가 10이라는 의미는, select * from user where team_id = (?,?,?) 이 쿼리로 예를 들자면 ? 의 개수가 10개가 될 수 있다는 것이다. 즉, team 객체의 개수가 100개라면 10번의 쿼리가 들어가는 것이다. BatchSize를 100으로 바꾸면 쿼리가 1번만 날라가게 된다. 

 

 

https://incheol-jung.gitbook.io/docs/q-and-a/spring/n+1 

 

N+1 문제 - Incheol's TECH BLOG

Query를 실행하도록 지원해주는 다양한 플러그인이 있다. 대표적으로 Mybatis, QueryDSL, JOOQ, JDBC Template 등이 있을 것이다. 이를 사용하면 로직에 최적화된 쿼리를 구현할 수 있다.

incheol-jung.gitbook.io

 

https://programmer93.tistory.com/83

 

JPA N+1 문제 해결 방법 및 실무 적용 팁 - 삽질중인 개발자

- JPA N+1 문제 및 해결 방법 - JPA를 사용하다 보면 의도하지 않았지만 여러 번의 select 문이 순식간에 여러 개가 나가는 현상을 본 적이 있을 것이다. 이러한 현상을 N+1문제라고 부른다. 해당 포스

programmer93.tistory.com

 

https://mighty96.github.io/til/fetch_fetchjoin/#2-fetch-%EC%A0%84%EB%9E%B5%EC%9D%84-lazy%EB%A1%9C-%EC%84%A4%EC%A0%95%ED%95%98%EB%A9%B4-n1%EB%AC%B8%EC%A0%9C%EA%B0%80-%EC%9D%BC%EC%96%B4%EB%82%98%EC%A7%80-%EC%95%8A%EB%8A%94%EB%8B%A4

 

[Spring JPA] fetch전략과 fetch join에 관한 오해

I. 개요 [Spring JPA] 프로젝트 중 게시판 쿼리 이슈를 해결하던 도중 굉장히 잘못된 지식을 갖고 있다는 것을 알게 되었다. 내용을 정리하는 편이 기억에 남을 것 같아서 글로 남기려 한다. II. 오해

mighty96.github.io

https://lng1982.tistory.com/297

 

17. [JPA] N+1 문제 (@BatchSize 해결 방안)

1번의 쿼리로 A라는 테이블의 데이터 100개를 가져왔다고 하자. 헌데 N번의 쿼리가 더 날아가는 상황이 발생했다. 내 의도는 한 번의 쿼리로 100개의 데이터를 가져오는게 전부인데 추가적으로 N번

lng1982.tistory.com

포스트맨 CSRF Failed: CSRF token missing or incorrect." 에러

얼마 전부터 포스트맨을 사용할 때 csrf token missing or incorrect 라는 에러가 지속해서 떴다. 이 경우 cookie 를 전부 지우고 다시 실행하면 되길래 매번 그렇게 했다. 그런데 번거롭기도 하고, 이유가 궁금하기도 해서 이를 찾아보고 해결했다.

 

내가 알아낸 원인은,

post 요청을 보낼 때마다 csrf token이 바뀌는데 자동으로 이가 업데이트 되지 않고 이전 csrf token이 남아있어서 포스트맨 내부에서 발생하는 것이라고 한다. 

 

아래 링크에 나온 방식대로 해결했다!

 

https://han-py.tistory.com/352

 

[postman] "detail": "CSRF Failed: CSRF token missing or incorrect." 에러 해결하기

해결하는데 3시간 정도 걸린거 같다..... 관련 경우의 수를 다 적어놨다... !!!! 하나씩 따라해보자. 문제 상황 포스트맨에서 아래와 같은 에러가 발생했다. 장고를 활용해서 DRF로 구현을 해서 postma

han-py.tistory.com

 

JSON 서버 구축

 

json 서버 배포를 해볼 일이 있어서 아래 방법으로 해봤다. 그런데 json 파일이 너무 크기가 커서 결국 써먹지는 못했다.

 

 

https://anerim.tistory.com/178

 

[Dev 개발] JSON 서버 구축하기 / 로컬에서 API JSON 서버 구축하기 / 로컬 서버 만들기

안녕하세요. 디자인도 하고, 개발도 하는 '디발자 뚝딱'입니다. 이번 포스팅에서는 프론트엔드 개발자가 로컬에서 JSON 서버를 구축하는 방법에 대해 다뤄보겠습니다. 보통은 백엔드 개발자가 만

anerim.tistory.com