TIL - 0819

Spring-data-JPA

상태

  • 기본적으로 트랜잭션 내에서 JPA/Hibernate가 관리하냐 마냐의 상태 - 관리 대상이 아니면 영속화와는 상관없이 gc 대상 객체일 뿐, 중간 계층이 존재함(성능적인 장점이 있음 - 커넥트를 여러번 하지않음)
    1. transient : 관리한 적이 없는 상태, 일반 객체(ID가 없음)
    2. persistence : 관리하고 있는 상태, 1차 캐시 대상, 트랜잭션 내에서 얘를 찾으면 SELECT 쿼리 날리는 대신 캐시한 객체를 리턴해줌
    3. detached : 트랜잭션이 끝나고 Persistence Context가 제거된 상태, 관리한 흔적으로 맵핑 데이터는 있으나 일반 객체
    4. removed : 트랜잭션 내에서 삭제된 상태임, Commit될 때 실제 삭제가 이뤄짐
Account account = new Account(); // 아직까지는 관리상태가 아님
accountRepository.save(account); // 관리 상태로 들어감 - 바로 INSERT 쿼리를 날리지않고, 트랜잭션 Commit 될 때 날림
  • 트랜잭션이 종료되면 Persistence Context를 없애버림
    • 캐시, lazy loading을 하지않음 : 그냥 객체 상태(매핑된 값이 있다고 하더라도)
    • 다시 관리하는 상태로 만들어줘야 Persistence Context가 생성되고 장점을 사용
    • 아래 예시는 트랜잭션(@Transactional)으로 묶지 않으면 각각의 독립된 트랜잭션임 그렇기대문에 LazyInitializationException 발생함 : 트랜잭션으로 묶어야 Lazy init이 가능함(프록시로 할당해두고 단일 쿼리가 끝난 이후 찾아가려면 찾아갈 방법이 없기때문에) 혹은 OSIV 패턴 이라는 방법이 있음(알아보기) 아니면 즉시로딩으로 바꿔버려도 되지만 성능상 이점을 볼 수 없기때문에
Post post = postRepository.findById(4L).get();
log.debug("Content : {}", post.getComments().stream().map(Comment::getContent).collect(joining(", ")));

cascade : 연관관계 상태 전이

  • 연관된 엔티티 객체(들)에 상태 변화를 전이 시킴 : 예를들어 A에서 cascade 타입 설정으로 PERSIST를 설정해두면 A와 연관된 객체들은 A가 저장(관리)될 때 같이 저장됨

  • 알아야할 것 : 외래키를 들고 있는 상태에서 REMOVE 상태 전파를 설정하지않고, 키를 들고 있지않은 쪽에서 자신을 삭제해달라고 요청할 경우 에러 발생 - 데이터 참조 무결성에 의해 키를 가지고 있는 쪽에서는 NULL이나 존재하는 pk를 가지는 것이 아닌게 되므로 삭제 요청을 하면 에러가 발생함, 수정에 대한 권한은 키를 가진 쪽에서(삭제를 할 때면 같이 삭제되도록(관계는 Post가 Comment보다 Parent지만 키는 Comment가 관리하는 것이 성능상 더 낫다)

  • 주요 설정값 : 하나씩 세세하게 설정할 수도 있고 ALL로 설정해서 모든 상태가 전이되도록 할 수도 있음

    1. PERSIST : 저장 시
    2. REMOVE : 삭제 시
    3. ALL

fetch : 연관관계에 있는 엔티티 객체 가져오는 전략

  • 조회 시 곧바로 가져오느냐, 필요할 때(사용할 때) 가져오느냐를 정함 : 연관관계에 따라 기본적을 설정되어있는 fetch type이 있음
    • Collection(Many)일 때는 Lazy로 설정되어있음 : 몇개일지도 모르는 상태에서 필요한지도 모르는데 가져오면 성능상 좋지않을 수 있으니
  • 전략
    1. EAGER : 곧바로 가져오기
    2. LAZY : 최초 사용 시 가져오기, 프록시 객체를 할당해둠
/* EAGER */
Hibernate: 
    select
        post0_.id as id1_2_0_,
        post0_.created_date as created_2_2_0_,
        post0_.title as title3_2_0_,
        comments1_.fk_comment_post as fk_comme4_1_1_,
        comments1_.id as id1_1_1_,
        comments1_.id as id1_1_2_,
        comments1_.created_date as created_2_1_2_,
        comments1_.content as content3_1_2_,
        comments1_.fk_comment_post as fk_comme4_1_2_ 
    from
        post post0_ 
    left outer join
        comment comments1_ 
            on post0_.id=comments1_.fk_comment_post 
    where
        post0_.id=?
        
/* Lazy */
Hibernate: 
    select
        post0_.id as id1_2_0_,
        post0_.created_date as created_2_2_0_,
        post0_.title as title3_2_0_ 
    from
        post post0_ 
    where
        post0_.id=?
  • 발생할 수 있는 문제점 : N+1 쿼리문제 - 지연로딩한 설정한 아이(Collection을 사용하는 Many에서는 기본적으로 설정되어있음)를 통해서 데이터를 읽을 때 로딩쿼리를 한번 날린 후 데이터를 읽는 쿼리가 하나하나씩 모두 발생하는 문제점
    • 아래 상황을 설정해두고 했으나 N+1 문제는 발생하지않아 당황스럽 해결방법을 적어둔 블로그에서 상정한 문제 상황을 그대로 구현했으나… : Hibernate가 N+1 문제를 자체적으로 처리한 것인지 찾아보기
    • 발생하는지 안하는지 알려면 생성하는 쿼리를 보는 습관을 들여야함 : 해주겠지가 아니라 시켰으면 잘하는지 봐야지
// 하나의 트랜잭션으로 묶음
Post post = postRepository.findById(4L).get();
log.debug("Content : {}", post.getComments().stream().map(Comment::getContent).collect(joining(", ")));

Hibernate: 
    select
        post0_.id as id1_2_,
        post0_.created_date as created_2_2_,
        post0_.title as title3_2_ 
    from
        post post0_
Hibernate: 
    select
        comments0_.fk_comment_post as fk_comme4_1_0_,
        comments0_.id as id1_1_0_,
        comments0_.id as id1_1_1_,
        comments0_.created_date as created_2_1_1_,
        comments0_.content as content3_1_1_,
        comments0_.fk_comment_post as fk_comme4_1_1_ 
    from
        comment comments0_ 
    where
        comments0_.fk_comment_post=?

JPQL

  • SQL과 비슷한 문법인데, 객체 모델 기준으로 쿼리를 작성할 수 있음
    • DB와는 독립적 : SQL로 변환됨
    • Typed Query 생성도 가능 : Object -> 특정 타입
TypedQuery<Post> query = session.createQuery("SELECT p FROM Post AS p", Post.class); // Post는 테이블이름이 아니라 엔티티 클래스 명
List<Post> posts = query.getResultList();
log.debug("포스트 개수 : {}", posts.size());