영속성 컨텍스트의 특징
조회
•
영속성 컨텍스트는 내부에 1차 캐시를 갖고있다.
•
영속상태의 Entity는 모두 1차 캐시에 저장된다.
1차 캐시 | Map |
@Id | Key |
Entity Instant | Value |
•
EntityManager.persist(Entity)는 1차 캐시에 데이터를 저장하고, 아직 DB에는 반영되지 않는다.
•
1차 캐시의 Key는 DB의 PK와 매핑된다.
EntityManager.find(Member.class, “memberId”);
Java
복사
•
위 명령은 1차 캐시에서 식별자로 Entity를 찾고, 해당하는 데이터가 없으면 DB에서 조회한다.
•
DB에서 조회된 데이터는 1차 캐시에 저장된다. (영속화)
Member memberA = EntityManager.find(Member.class, “memberId”);
Member memberB = EntityManager.find(Member.class, “memberId”);
memberA == memberB -> true
Java
복사
•
같은 식별자를 통해 두 개의 Entity를 초기화한다면, 1차 캐시의 데이터(Entity Instant)를 반환하여 두 Entity 객체는 동일성이 보장된다.
저장
•
EntityManager가 persist()를 수행하면, 1차 캐시에 Entity를 저장과 동시에 EntityManager 내부의 쓰기 지연 SQL 저장소에 Insert SQL을 모아둔다.
•
이후 EntityManager가 Commit()을 수행하면 내부의 쓰기 지연 SQL 저장소의 SQL들을 DB로 Flush 한다.
•
이를 Transaction을 지원하는 쓰기 지연(transactional write-behind)라 한다.
em.transaction.commit() -> 쓰기 지연 SQL 저장소의 SQLs을 DB로 Flush-> DB.commit
Java
복사
수정
•
JPA는 Entity를 수정할 때, Entity를 조회하고, 데이터를 변경하기만 하면 된다.
Member memberA = em.find(Member.class, "memberA");
memberA.setUsername("modifyName");
// em.update(member); <- EntityManager는 update()라는 메소드를 갖고있지 않다.
Java
복사
•
Entity 수정을 위해 update()같은 메소드를 사용해야 할 것 같지만, 그런 메소드는 갖고있지 않다.
•
Entity 객체의 데이터를 수정하고 EntityManager가 flush() 메소드를 수행하면 자동으로 update된다.
•
이를 변경 감지(dirty checking)라 한다.
•
변경 감지는 1차 캐시의 스냅샷을 통해 이루어진다.
•
Entity의 데이터를 변경 후 EntityManager.flush() 하게되면, Entity와 스냅샷을 비교하여 1차 캐시의 변경된 Entity를 찾는다.
•
변경된 Entity가 있으면, Update SQL을 쓰기 지연 SQL 저장소에 보낸다.
•
쓰기 지연 SQL 저장소의 SQL을 DB에 반영 이후 DB Transaction을 Commit 한다.
•
이렇게 변경 감지를 통해 실행된 Update SQL은 수정된 데이터만 update 하지 않는다.
•
JPA에서의 수정은 Entity의 모든 필드를 Update하고 이것이 기본 전략이다.
•
이런 방식은 DB로 전달하는 데이터의 전송량이 증가하는 단점을 갖지만, 다음과 같은 장점을 갖는다.
◦
모든 필드를 수정하는 Update SQL은 항상 동일하다. (바인딩되는 데이터는 다름)
▪
Application이 로딩되는 시점에 Update SQL을 미리 생성하고, 재사용할 수 있다.
◦
DB에 동일한 SQL을 보내면 DB는 이전에 파싱해둔 쿼리를 재사용 할 수 있다.
•
필드가 많거나, 저장되는 데이터가 너무 크다면 수정된 데이터만으로 동적 Update SQL을 생성하는 전략을 선택하면 된다.
•
이 방법은 Hibernate의 확장 기능을 사용해야 한다.
@Entity
@Table(name = "Member")
@org.hibernate.annotations.DynamicUpdate
public class Member { ... }
Java
복사
•
Hibernate의 DynamicUpdate 어노테이션을 사용하면 변경된 데이터만 수정하는 동적 Update SQL을 생성한다.
◦
저장의 경우에도 존재하는 데이터만(null이 아닌) 필드만 SQL에 반영하는 @DynamicInsert 어노테이션도 있다.
참고
상황에 따라 다르지만 Column이 30개 이상이 되면 @DynamicUpdate를 통한 수정이 더 빠르다고 한다.
정확한 성능은 본인의 환경에서 직접 테스트를 해보는 것이다.
권장하는 방법은 기본전략을 사용하되, 최적화가 필요할 정도로 느려지면 그 때 전략을 수정하는 것이다.
* 한 테이블에 컬럼이 30개 이상이 된다는 것은 설계상 책임이 적절하게 분배되지 않았다고 볼 수 있다.
삭제
Memeber memberA = em.find(Member.class, "memberA");
em.remove(memberA);
Java
복사
•
em.remove()에 삭제할 Entity를 넘겨주면 Entity를 삭제한다.
•
물론 Entity를 즉시 DB에서 삭제하지 않는다.
•
쓰기 지연 SQL 저장소에 Delete SQL을 등록하고,
•
Transaction을 commit()하고 flush()를 호출하면 DB에 Delete SQL을 전달한다.
•
em.remove()를 수행하는 순간 해당 Entity는 영속성 컨텍스트에서 제거된다.
•
이렇게 제거된 Entity는 재사용하지 않는 것이 권장된다.