development/spring

JPA 참고사항 정리

bokshiri 2023. 9. 15. 05:35

※ 참고사항 정리

 

1. Entity와 Dto 매핑을 위한 MapStruct(Lombok) - @QueryProjection을 사용하여 DTO에 바인딩 할 수도 있고,

Lombok에서 제공하는 MapStruct을 사용하여 엔티티가 아닌 원하는 DTO에 데이터를 바인딩하도록 설정 가능 (필드 이름이 상호 일치하지 않을 경우 @Mapping 어노테이션으로 매핑 규칙 정의)

 

참고: https://blog.naver.com/n_cloudplatform/222957490406

 

2. 대량 로우 업데이트의 경우 영속성 컨텍스트(EntityManager)에서 객체를 가져와 건건이 update를 할 수 있으나 (대상건을 먼저 영속성 컨텍스트에서 조회한 후 식별자로 객체를 찾아 for문으로 건건이 처리)

   영속성 컨텍스트에 직접 접근하지 않고 jpql로 쿼리를 날려 처리할 수도 있다.

   하지만 이렇게 처리할 경우 Persistence Context의 객체와 실db간 동기화가 이루어지지 않기 때문에 별도의 처리가 필요함

   - 만약 member 테이블의 나이 컬럼을 전체 10으로 업데이트 할 경우, Persistence Context의 객체는 이전에 조회하여 캐싱되어 있는 데이터가 남아있으나

     실 DB에는 10으로 모두 변경이 된 상태이다. 이 상태에서 EntityManager(key)로 조회를 할 경우 변경되기 이전에 캐싱된 데이터가 조회되는 문제가 발생함

    따라서 위같은 케이스를 dbms에 update일괄처리 할 경우(bulk update) 업데이트 후 entityManager.flush(), clear()를 호출해줘야 한다.

    

    ※ 주의사항: bulk update 메서드에 @Modifying 어노테이션 사용 필요

              SpringDataJPA가 해당 리포지토리 메소드가 SELECT문인지 UPDATE문인지 알아야 Return Type을 결정하는데, 

              아직은 내부적으로 Query만 보고 판단을 못해서, 명시를 해줘야 Return type을 결정하기 때문

            

            @Modifying(clearAutomatically = true) 형태로 flush(), clear() 대체 가능

            

참고: https://jaehoney.tistory.com/151

 

3. JPQL은 RDB의 테이블이 아닌, 객체를 대상으로 query를 날린다. QueryDSL역시 선언 방식만 다를 뿐 컴파일하면서 JPQL로 변환되기 때문에 JPQL로 실행된다.

   즉 Dirty Checking(변경 감지)을 제외하고는 전부 JPQL - 객체 지향 쿼리 언어로 실행된다고 보면 된다.

   ※주의사항: JPQL이 객체 지향 쿼리언어라고 해서 영속성 컨텍스트의 객체를 직접 컨트롤하는 것은 아니다.

            여기서 의문이 드는 점은, 객체지향 쿼리언어에서의 '객체'는 무엇을 의미하는가?..

         영속성 객체를 직접 컨트롤하는 것이 아니라면 실 db를 대상으로 작업을 한다는 의미인지.. 그리고 JPQL의 경우 쓰기지연 SQL 저장소에 SQL을 저장하는 것이 아니라

         호출 시점에 즉시 실행하는 것인지.. 확인 필요 

         ===> JPQL은 객체 지향 쿼리 언어이지만 호출이 됐을 때 flush() 후 실 db의 데이터를 변경한다.(쿼리가 바로 실행됨)

             위와 같은 특성으로 인해 영속성 객체는 변경되지 않음으로써 영속성 컨텍스트와 db 사이의 동기화 문제가 발생한 것.

         

   

   * Dirty Checking이란? 영속성 컨텍스트에서 관리되는 객체의 상태가 변경될 경우 

     트랜잭션 종료 시점(commit 시점)에 Dirty Checking 후 entityManager가 쿼리를 자동 생성 후 쓰기지연 SQL 저장소에 SQL 저장 후 flush statement - commit 을 함으로써 실 DB에 객체의 내용을 반영한다.

    

4. flush 시점

   JPA에서 flush 시점은 총 4가지로 정리할 수 있다

   

   1. entityManager.flush() 호출을 통해 직접 처리하는 경우

   2. 트랜잭션 commit 시점에 호출

   3. JPQL이 선언된 경우 JPQL 메서드 처리 직전에 호출

   4. 엔티티에 컬럼매핑시 시퀀스제네레이트 타입이 IDENTITY일 경우는 persist 실행시 flush가 동작함

   

   flush를 하게되면 JPA는 Dirty Checking(변경 감지)를 통해 영속성 컨텍스트의 스냅샷을 비교해 수정된 엔티티를 파악한 후 쓰기지연 SQL 저장소에 SQL문 등록 및 flush로 쿼리를 실행한다.

   (물론 커밋은 트랜잭션 종료 시점에 일괄처리. 만일 트랜잭션이 잡혀있다면 해당 세션에서만 변경된 값이 조회될 것.)

   JPA는 반드시 트랜잭션 단위로 처리되어야 한다.

   ※flush가 일어난다고 해서 1차 캐시가 삭제되는 것은 아님(쓰기지연 SQL 저장소의 SQL이 실행될 뿐)

   

   ※의문: 영속성 객체 등록 후 flush를 하게되면 쿼리가 실행되면서 DB와 동기화가 이루어질텐데, 그 이후 해당 트랜잭션이 롤백되면 DB데이터는 날아가지만 영속성 객체는?

       사용자가 별도로 entityManager를 컨트롤해야 하는지.. JPA에서 자동으로 처리해주는지 

       ===> 트랜잭션이 롤백되면 영속성 객체가 1차 캐싱을 하며 등록해놓은 스냅샷을 기준으로 객체도 함께 롤백되는 것 같음(별도의 처리로직 없다는 답변 받음)

   

참고: https://ssdragon.tistory.com/m/61

 

5. 톰캣은 기본적으로 multi-thread를 지원

   JPA에서는 스레드가 생성될 때마다 EntityManagerFactory에서 EntityManager를 생성한다.

   EntityManager는 Persistence Context를 컨트롤하는 주체 - DB의 커넥션 풀에서 커넥션을 가져와 DB에 접속한다.

   

   Persistence Context(영속성 컨텍스트)와 entityManager(관리 주체)의 관계?

   - 기본적으로 영속성 컨텍스트는 트랜잭션 단위로 이해(라이프 사이클이 동일함)

   - 스프링 컨테이너는 멀티 스레딩 환경에서 트랜잭션을 분리하기 때문에 스레드별로 트랜잭션이 별도로 동작함

   - 즉, 스레드별로 EntityManager가 생성되고, 트랜잭션이 분리되므로 영속성 컨텍스트도 모두 별도로 분리되어 바라보게됨

   

참고: https://www.nowwatersblog.com/jpa/ch13/13-1

    https://ict-nroo.tistory.com/130

    

6. JPA 더티체킹 업데이트는 기본적으로 테이블의 모든 컬럼을 업데이트 한다.

   부분 업데이트(변경한 필드만 대상으로 함)를 원하는 경우 엔티티에 @DynamicUpdate를 붙인다.

   

참고: https://jojoldu.tistory.com/415

 

  1. JPA에서는 1:N 관계의 경우 항상 outer join을 사용한다. (이너 조인을 사용할 경우 조회되지 않는 데이터가 발생할 것을 염두에 두다.) 

참고: https://velog.io/@ayoung0073/JPA-%ED%94%84%EB%A1%9D%EC%8B%9C-%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84-%EA%B4%80%EB%A6%AC

 

7. JPA에서 제공하는 Repository는 모두 트랜잭션을 지원한다.

가령, @Transactional이 없는 메서드에서 .save() 호출시 Spring JPA에서 트랜잭션을 걸어줌.

참고: https://joanne.tistory.com/218