JPA - 영속성 컨텍스트와 JPQL

2019. 2. 12. 00:19Web/JPA

JPA - 영속성 컨텍스트와 JPQL



쿼리 후 영속 상태인 것과 아닌것

-JPQL의 조회 대상은 엔티티, 임베디드 타입, 값 타입 같이 다양하다. 하지만 엔티티를 조회하면 영속성 컨텍스트에 관리되지만 나머지는 관리되지 않는다. 예를 들어 임베디드 타입만 참조해서 조회했을 경우에 값을 변경해서 플러시 시점에 반영되지 않는다. 하지만 엔티티를 조회하여 엔티티가 가지고 있는 임베디드타입을 변경했을 경우에는 플러시 시점에 변경이 반영이 된다.



JPQL로 조회한 엔티티와 영속성 컨텍스트

-JPQL로 데이터베이스에서 조회한 엔티티가 영속성 컨텍스트에 이미 있으면 JPQL로 데이터베이스에서 조회한 결과를 버리고 대신에 영속성 컨텍스트에 있던 엔티티를 반환한다. 이때 식별자 값을 이용하여 비교한다.


여기서 동일하다는 것은 엔티티의 모든 필드값이 동일하다는 것을 의미하는 것이 아닌 엔티티의 식별자값이 동일함을 의미한다. 즉, 만약 1차 캐시의 member1의 이름이 "여성게"이고 , 디비에 있는 member1의 이름이 "소라게"여도 식별자 값이 같음으로 "여성게"로 조회가 되게 된다. (물론 이런 상황 자체가 있는 것이 잘못된 것이긴함) 하지만 만약 영속성컨텍스트에 디비에서 조회한 엔티티가 존재하지 않으면 디비에서 가져온 엔티티를 영속성 컨텍스트에 넣는다. 왜 그런가?


1. 새로운 엔티티를 영속성 컨텍스트에 하나 더 추가한다. -> 식별자값은 유일함으로 이것은 말이안됨.

2.기존 엔티티를 새로 검색한 엔티티로 대체한다. -> 그럴싸 하지만 만약 엔티티가 수정중에 있다면 아주 큰문제가됨.

3.기존 엔티티는 그대로 두고 새로 검색한 엔티티를 버린다. -> 이 규칙을 따르게 되는 것이다. 영속성 컨텍스트는 영속 상태인 엔티티의 동일성을 보장해야하기 때문 




find() vs JPQL

-em.find() 메소드는 엔티티를 영속성 컨텍스트에서 먼저 찾고 없으면 데이터베이스에서 찾는다. 따라서 해당 엔티티가 메모리에 존재한다면(이미 이전에 동일한 엔티티를 조회한 적이 있기때문) 성능상 이점이 있다.

JPQL은 항상 디비에서 찾는다. 왜냐하면 JPQL은 SQL로 변환되어 데이터베이스에 바로 날려보기 때문이다. 항상 디비에서 찾지만 디비에서 찾은 엔티티가 영속성 컨텍스트에 존재한다면 디비에서 찾은 엔티티를 버리고 영속성 컨텍스트에 있는 것을 반환값으로 넘긴다.



JPQL과 플러시 모드

-플러시는 영속성 컨텍스트의 변경 내역을 디비에 동기화하는 것이다. JPA는 플러시가 일어날 때 영속성 컨텍스트에 등록, 수정, 삭제한 엔티티를 찾아서 적절한 SQL을 만들어 디비에 반영한다.

플러시 모드
1)FlushModeType.AUTO : 커밋 또는 쿼리 실행 시 플러시(기본값)
2)FlushModeType.COMMIT : 커밋시에만 플러시


쿼리와 플러시모드

JPQL은 영속성 컨텍스트에 있는 데이터를 고려하지 않고 디비에서 데이터를 조회한다. 따라서 JPQL을 실행하기 전에 영속성 컨텍스트의 내용을 디비에 반영해야한다. 그렇지 않으면 의도하지 않은 결과가 발생할 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void batchQuery() {
     JPAQuery query = new JPAQuery(em);
     QMemberJPQL qMember = QMemberJPQL.memberJPQL;
     tx.begin();
     JPAUpdateClause updateBatch = new JPAUpdateClause(em, qMember);
     long count = updateBatch.where(qMember.username.eq("여성게1"))
                             .set(qMember.username,"바보멍청이").execute();
               
     tx.commit();
     if(count == 1) {
             MemberJPQL member = query.from(qMember)
                                      .where(qMember.username.eq("바보멍청이")).uniqueResult(qMember);
            System.out.println("=====================batchQuery======================");
             if(!ObjectUtils.isEmpty(member)) {
                     System.out.println(member.toString());
             }
             System.out.println("=====================batchQuery======================");
     }
}
cs


이 예제는 QueryDSL의 수정 배치 쿼리이다. 이 예제의 결과가 어떻게 될것인가? 결과는 정말 의도치 않게 나오게 된다. QueryDSL의 배치쿼리는 영속성컨텍스트와 관계없이 직접 디비로 요청을 보내게 된다. 이러는 순간 디비와 영속성컨텍스트의 동기화는 깨진것이다. 변경을 완료했고 나는 변경된 이름으로 쿼리를 날렸는데, 이게 뭐람 변경전의 "여성게1"이란 결과가 나오게 된다. 이유는 위에서 설명한 것과 같이 영속성컨텍스트에 이미 존재하기 때문에 디비에서 가져온 엔티티가 데이터가 달라도 식별자 값이 같기 때문에 버려지게 되고 수정전에 이름이 출력되게 되는 것이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void batchQuery2() {
        JPAQuery query = new JPAQuery(em);
        QMemberJPQL qMember = QMemberJPQL.memberJPQL;
        tx.begin();
        JPAUpdateClause updateBatch = new JPAUpdateClause(em, qMember);
        long count = updateBatch.where(qMember.username.eq("여성게1"))
                                                .set(qMember.username,"바보멍청이").execute();
        MemberJPQL member2 = query.from(qMember)
                                .where(qMember.username.eq("바보멍청이")).uniqueResult(qMember);
        member2.setUsername("바보멍청이");
        
        tx.commit();
        if(count == 1) {
                
                MemberJPQL member = new JPAQuery(em).from(qMember)
                                    .where(qMember.username.eq("바보멍청이")).uniqueResult(qMember);
                System.out.println("=====================batchQuery======================");
                if(!ObjectUtils.isEmpty(member)) {
                        System.out.println(member.toString());
                }
                System.out.println("=====================batchQuery======================");
        }
        
}
cs


이 코드는 실무에서는 사용할 수없는 코드이다. 하지만 직관적으로 영속성 컨텍스트의 값을 수정배치쿼리와 동일하게 변경한것을 보여주기 위해 짠것이다. 이 예제의 결과는 수정배치에서 변경된 값과 동일한 엔티티가 반환되게 된다.