개발/JPA

영속성과 영속성컨택스트

낭곰 2022. 7. 13. 11:03
반응형

 

 

JPA에서의 영속성

  JPA의 핵심 내용은 엔티티가 영속성 컨텍스트에 포함되어 있냐 아니냐로 갈립니다. JPA의 엔티티 매니저가 활성화된 상태로 트랜잭션(@Transactional) 안에서 DB에서 데이터를 가져오면 이 데이터는 영속성 컨텍스트가 유지된 상태입니다.

  이 상태에서 해당 데이터 값을 변경하면 트랜잭션이 끝나는 시점에 해당 테이블에 변경 내용을 반영하게 됩니다. 따라서 우리는 엔티티 객체의 필드 값만 변경해주면 별도로 update()쿼리를 날릴 필요가 없게 됩니다. 이 개념을 더티 체킹이라고 합니다.

 

  영속상태란, 영속성 컨텍스트가 관리하는 엔티티

  결국 영속 상태라는 것은 영속성 컨텍스트에 의해 관리된다는 뜻이 됩니다.

 

 

영속성 컨텍스트란?

  영속성 컨텍스트(persistence context) 는 '엔티티를 영구 저장하는 환경' 이라는 뜻입니다. EntityManager 를 이용해 Entity 를 저장하거나 조회할 때 EntityManager 는 영속성 컨텍스트에 Entity 를 보관하고 관리합니다.(1차 캐시) EntityManger객체.persist(Entity객체) 를 실행하면 영속성 컨텍스트가 Entity 를 관리하게 됩니다.

  영속성 컨텍스트는 눈에 보이지 않는 논리적인 개념입니다. 또한 EntityManager 를 하나 생성할 때 하나가 만들어지며, EntityManager 를 통해 접근할 수 있고 관리할 수 있습니다.

 

 

영속성 컨텍스트의 특징은 다음과 같습니다.

 

  • 영속성 컨텍스트는 Entity 를 식별자 값으로 구분합니다.
    Entity에서 @Id 어노테이션을 통해 지정한 멤버변수가 영속성 컨텍스트에서 식별자 값으로 저장됩니다.(따라서 영속 상태는 반드시 식별자 값이 있어야 합니다.)

  • JPA는 보통 트랜잭션을 flush하는 순간 영속성 컨텍스트에 새로 저장된 Entity를 데이터베이스에 반영합니다.(트랜잭션 commit시 flush 가 자동호출 됩니다.)

 

영속성 컨텍스트가 엔티티를 관리하면 다음과 같은 장점이 있습니다.

 

1. 1차 캐시를 이용합니다.
  영속성 컨텍스트는 내부에 캐시를 가지고 있있으며 이를 1차 캐시(Map과 비슷한)라고 합니다. 영속 상태의 Entity는 모두 이곳에 저장되며 키는 @Id 로 매핑한 식별자이며, 값은 Entity 인스턴스입니다.

  entityManager.find() 메소드를 호출하면 먼저 1차 캐시에서 Entity를 찾고, 만약 찾는 Entity 가 1차 캐시에 없으면 데이터베이스에서 조회한 후 1차 캐시에 저장하고 영속 상태인 해당 객체를 반환합니다.

 

 

2. 객체의 동일성을 보장합니다.

ex)

Member a = em.find(Member.class, “member1”);
Member b = em.find(Member.class, “member1”);

여기서 a == b는 true를 리턴합니다.

 

em.find(Member.class, “member1”)

를 반복해서 호출해도 영속성 컨텍스트는 1차 캐시에 있는 같은 Entity 인스턴스를 반환합니다. 따라서 둘은 같은 인스턴스고 결과는 당연히 참입니다. 따라서 영속성 컨텍스트는 성능상 이점과 엔티티의 동일성을 보장합니다.

 

 

3. 트랜잭션을 지원하는 쓰기 지연(transactional write-behind)을 수행합니다.

  EntityManager 는 트랜잭션을 commit 하기 전까지 데이터베이스에 Entity 를 저장하지 않고 영속성 컨텍스트 내부의 쓰기지연 SQL 저장소에 쿼리를 차곡차곡 모아둡니다. 이 후 commit 을 하게 되면 저장해두었던 쿼리를 데이터베이스에 보내게 되는데, 이것을 트랜잭션을 지원하는 쓰기 지연이라고 합니다.트랜잭션을 commit 하면 EntityManager 는 영속성 컨텍스트를 flush() 합니다.

  flush() 는 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 작업으로 등록, 수정, 삭제한 Entity 를 데이터베이스에 반영합니다.

  좀 더 구체적으로 이야기하면, 쓰기 지연 SQL 저장소에 모인 쿼리를 데이터베이스에 보냅니다. 이러한 동기화 작업을 거친 후 실제 데이터베이스 트랜잭션을 commit 합니다.

 

  트랜잭션을 지원하는 쓰기 지연이 가능한 이유

  

  다음 로직을 2가지 경우로 생각해보면,

begin() // 트랜잭션 시작

save(A);
save(B);
save(C);

commit(); // 트랜잭션 커밋

  1) 데이터를 저장하는 즉시 등록 쿼리를 데이터베이스에 보냅니다. 예제에서 save() 메서드를 호출할 때마다 즉시 데이터베이스에 등록 쿼리를 전달합니다. 그리고 마지막에 트랜잭션을 커밋합니다.
  2)) 데이터를 저장하면 등록 쿼리를 데이터베이스에 보내지 않고 메모리에 모아둡니다. 그리고 트랜잭션을 커밋할 때 모아둔 등록 쿼리를 데이터베이스에 보낸 후에 커밋합니다.

 

  트랜잭션 범위 안에서 실행되므로 둘의 결과는 같습니다. A, B, C 모두 트랜잭션을 커밋하면 함께 저장되고 롤백하면 함께 저장되지 않습니다. 등록 쿼리를 그때 그때 데이터베이스에 전달해도 트랜잭션을 커밋하지 않으면 아무 소용이 없습니다. 어떻게든 커밋 직전에만 데이터베이스에 SQL을 전달하면 됩니다. 이것이 트랜잭션을 지원하는 쓰기 지연이 가능한 이유입니다.

  이 기능을 잘 활용하면 모아둔 등록 쿼리를 데이터베이스에 한 번에 전달해서 성능을 최적화 할 수 있습니다. 

 

 

4. 변경을 감지합니다.

  영속성 컨텍스트에는 이전 flush() 때의 Entity 상태를 복사해서 저장해둔 스냅샷이 존재합니다.

  JPA는 flush() 시점에 스냅샷과 Entity를 비교해 변경된 Entity를 찾습니다. 만약 존재한다면 각각의 객체에 대한 수정 쿼리를 만들어 쓰기 지연 SQL 저장소에 저장한 후 한꺼번에 데이터베이스로 보내고 데이터베이스 트랜잭션을 commit 합니다.

  여기서 수정 쿼리는 Entity의 모든 필드를 업데이트합니다. 이렇게 하면 수정 쿼리를 애플리케이션 로딩 시점에 미리 생성해두고 재사용할 수 있으며, 데이터베이스에 동일한 쿼리를 보낼 때 데이터베이스가 이전에 한 번 파싱된 쿼리를 재사용할 수 있습니다.

  변경 감지는 영속성 컨텍스트가 관리하는 영속 상태의 엔티티에만 적용됩니다. 바꿔 말하면, 준영속 상태의 객체는 아무리 수정해도 영속성 컨텍스트가 변경을 감지하지 못합니다.

 

※ 상황에 따라 다르지만 컬럼이 대략 30개 이상이 되면 기본 방법인 정적 수정 쿼리보다 @DynamicUpdate를 사용한 동적 수정 쿼리가 빠르다고 합니다. 가정 정확한 것은 본인의 환경에서 직접 테스트 해보는 것입니다.

 

 

5. 지연 로딩을 수행합니다.

  지연 로딩(Lazy Loading) 이란 실제 객체 대신 프록시 객체를 로딩해두고 해당 객체를 실제 사용할 때 영속성 컨텍스트를 통해 데이터를 불러오는 방법입니다.

Member member = em.find(Member.class, "member1");
// SELECT * FROM MEMBER WHERE MEMBER_ID = 'member1'

Team team = member.getTeam();
String teamName = team.getName(); 
// SELECT * FROM TEAM WHERE TEAM_ID = 'team1'

 

em.find(Member.class, "member1")에서는 Member 객체에 대한 SELECT 쿼리만 날립니다.

Team team = member.getTeam()로 Team 객체를 가져온 후에 team.getName()처럼 실제로 team 객체를 사용할 때,

즉, 값이 실제로 필요한 시점에 JPA가 Team에 대한 SELECT 쿼리를 날립니다.

 

※조회 대상이 영속성 컨텍스트에 이미 있으면 프록시 객체를 사용할 이유가 없습니다. 따라서 프록시가 아닌 실제 객체를 사용합니다.

 

  결국 연관된 엔티티를 즉시 로딩하는 것이 좋은지 아니면 실제 사용할 때까지 지연해서 로딩하는 것이 좋은지는 상황에 따라 다릅니다.

 

 

 

참고 : 자바 ORM 표준 JPA 프로그래밍(김영한 저)

반응형