BackEnd/패캠

[JPA] 트러블이슈

제이드Jade 2022. 2. 9. 15:31

<로딩 시점>

- 한 엔티티(b)와 연관관계를 맺는 엔티티(b)가 있다면, ab를 호출 때만 b가 조회하는 것은 지연로딩(lazy fetch)이고 그런 것 상관 없이 a가 호출될 때 b도 항상 같이 조회(eager fetch)하는 것이 즉시 로딩이다.

- a엔티티의 b속성 위에 @ManyToOne(fetch=FetchType.EAGER) 식으로 로딩 시점을 작성해줄 수 있다.

 

1. 즉시 로딩(EAGER)

           a만 조회해도 b가 실제 객체로 로딩이 된다.

           a를 조회할 때 b까지 조회하므로 b를 이용할 땐 캐쉬에서 가져온다. 따라서 select문이 따로 실행되지 않는다.

 

2. 지연 로딩(LAZY)

           a엔티티를 조회할 땐 bproxy객체로 가져온다. 후에 b를 실제로 사용하는 시점에 b가 조회되는 쿼리가 실행되어 b가 초기화된다. 그 이후에 b를 또 사용할 땐 캐쉬에서 가져온다.

ㄴ 하지만 이것도 세션이 열릴 때, 즉 영속성 컨텍스트가 엔티티를 관리할 때, (=트랜잭션이 열려있을 때=@Transactional) b가 초기화가 될 수 있다.

ToString.Exclude를 하지 않으면 lazy로딩이라 해도 출력할 때는 쿼리가 발생

 

- 디폴트

@OneToOne, @ManyToOne : EAGER

@OneToMany, @ManyToMany : LAZY

 

 

<N + 1 이슈>

- 하지만 두 방법 다 b의 수만큼 b의 조회가 일어난다. => n+1이슈

   => a를 조회하는 하나의 select문으로 연관된 모든 b까지 가져오게 하자.

           ㄴ 해결방법1) jpql join문이 적용된 select문을 만들어 findall 대신 사용.

                                  @Query(“select distinct r from Review r join fetch r.속성이름”)

                       - a를 조회할 때 join문을 추가하여 ab를 자동 매치함.

                       - lazy fetch라도 처음에 다 가져옴.

                       - inner join

                       - cf. 일반조인 : 연관된 b는 함께 조인되지 않는다.

           ㄴ 해결방법2) @EntityGraphfindAll에 적용하여 findAll를 오버라이딩해서 사용

                                  @EntityGraph(attributePaths=”속성이름”)          

                       - left outer join

              ( * 쿼리 메소드에도 물론 적용가능 @Query(“select r from Review r”)          //findAlljpql)

 *해결방법 1에서 distinct를 걸어주지 않으면 sql문의 join문이 그렇듯, 두 엔티티가 모두 기준이기 때문에 b의 수만큼 a-b가 매칭되어 결과로 나온다.

근데 sql과는 다르게 어차피 ab의 관계를 이미 다 맺었고 하나의 a만 가져와도 b와의 관계는 다 나타내주기 때문에 ditinct로 중복된 a를 제거해도 된다.

* 해결방법2left outer join이므로 왼쪽 엔티티 기준으로 출력된다. 따라서 왼쪽에 중복이 없다.

 

* 영속성 컨텍스트 이슈 : db와 영속성 컨텍스트(캐시) 간의 불일치.

ex 1) 앞서 살펴본 null, new Adress().        

=> 캐쉬에서 가져오면 통과, db에서 가져오면(=clear를 하면) 두번째는 실패.

 assertAll(

        ()->assertThat(userRepository.findById(7L).get().getHomeAddress()).isNull(),

        ()->assertThat(userRepository.findById(8L).get().getHomeAddress()).isInstanceOf (Address.class));

- assertAll : 여러개의 테스트가 독립적으로 병행수행

- ()-> : ()에는 메서드 내에서 쓰는 매개변수를 쓴다. == 함수이름() {return ~}

- assertThat().검증메서드 : 값이 검증메서드를 통과하는지 비교

경우2) 제약조건이 datetime인 필드에 now()를 넣어 제약조건과 다른 자릿수(now ms3자리)의 시간을 setter로 넣고 출력해본다면?

           ㄴ 캐쉬를 사용할 땐 now 그대로 표현, clear 후에 하면 제약조건대로 변경되어 표현

           ㄴ 비슷한 경우로, 속성의 default(여기선 now)@Query로 설정하고 setter로는 시간을 입력 안했을 때 캐쉬에는 null, clear후에는 default값이 적용되어 나옴

 

- 그외) wheredelete작업을 했다면 해당 delete된 엔티티와 연관을 맺으려 하면 오류가 남

 

- 배치쿼리 성능 이슈

- @Transactional : 해당 트랜잭션 범위가 영속성 컨텍스트의 범위가 되는 것. => 영속성있는 객체가 변경되면 더티체크로 인해 변경된 부분을 db에 업데이트한다.

           ㄴ 그래서 set만 하더라도 update문이 발생한다.

           ㄴ 즉, 계속 더티체크가 일어나서 데이터가 매우 많을 시에는 성능이 저하됨. => 배치쿼리 성능 이슈

           ㄴ 해결방법) @Transactional의 속성으로 readOnly=true로 해준다면 배치쿼리 성능 이슈가 발생하지 않음       - spring transcational에만 있는 속성

           ㄴ 대신, 캐쉬에는 변경이 반영될 지 몰라도 db에는 save를 하기 전엔  반영되지 않음.

( cf. 당연한 말이지만 @Transactional이 없거나 새로 생성만 한다면 save를 명시해야 저장이 된다.)