Java & Spring

[JPA] Entity Lifecycle과 Dirty Checking

ju_young 2024. 1. 3. 16:28
728x90

Persist

Article article = new Article(content); // Transient/New  
em.persist(article); // Managed(영속)
  • Managed(영속) 상태Persistence Context가 관리할 수 있는 상태를 의미한다.
  • Managed(영속)된 엔티티들은 flush가 호출되면 EntityManager가 변경을 감지하여 자동으로 update/insert 쿼리를 수행한다.

Detach

Managed(영속) 상태 의 엔티티가 Persistence Context에서 detach된 것을 준영속 상태라 한다.

em.detach(article);  
article.setContent(anotherContent);
  • Managed(영속) 상태 에서 detach 메소드를 호출하면 해당 엔티티는 준영속 상태가 되어 더 이상 Persistence Context에 의해 관리되지 않는다.
  • 준영속 상태가 되는 경우는 다음과 같이 여러 상황이 존재한다.
    • Transient/New 엔티티의 id가 Persistence Context에는 없고 DB에는 존재할때 해당 엔티티는 준영속 상태이다.
    • Transient/New 엔티티의 id가 Persistence Context에 있고 DB에는 존재하지않을때 해당 엔티티는 준영속 상태이다.
    • EntityManager로부터 find메소드를 통해 엔티티를 찾으면 Managed(영속) 상태가 되고 이후 detach 메소드를 호출하면 준영속 상태가 된다.
    • EntityManager로부터 find메소드를 통해 엔티티를 찾으면 Managed(영속) 상태가 되고 이후 close 메소드를 호출하면 준영속 상태가 된다.

정리

  • Transient/New 엔티티의 id가 Persistence Context, DB 중 한 쪽에만 존재할 때 준영속 상태
  • EntityManager로부터 find메소드를 통해 엔티티를 찾고 detach 또는 close 메소드를 호출하면 준영속 상태

Merge

Article article = new Article(id, content);  
em.merge(article);
  • 준영속 상태의 엔티티를 Persistence Context 또는 DB에 업데이트
  • Transient/New 상태의 엔티티를 merge할때 Persistence Context, DB에 아무것도 존재하지 않는다면, Managed(영속)화된 엔티티를 생성하여 반환한다.

기본 동작

  1. merge 메소드를 호출하면 member 엔티티의 id가 있는지 확인한다. (SELECT문 실행)
  2. Persistence Context에 id가 있다면 4.을 수행한다. 그렇지 않다면 DB에 해당 id의 엔티티를 요청한다.
  3. 응답받은 엔티티를 Persistence Context에 저장한다.
  4. merge한 엔티티를 Persistence Context에 반영한다.
  5. Managed(영속) 상태의 엔티티를 반환한다. 이때 merge로 전달된 엔티티는 준영속 상태이다.

Remove

em.remove(comment);
  • Persistence Context에서 엔티티가 삭제되고 트랜잭션 종료시 DB에서도 삭제된다.

Dirty Checking

JpaRepository의 구현 클래스 중 SimpleJpaRepository의 save 메소드를 확인하면 다음과 같다.

@Transactional
public <S extends T> S save(S entity) {
    Assert.notNull(entity, "Entity must not be null.");
    if (this.entityInformation.isNew(entity)) {
        this.em.persist(entity);
        return entity;
    } else {
        return this.em.merge(entity);
    }
}

 

entityInformation의 isNew를 확인하여 해당 엔티티가 새로 만들어진 엔티티인지 아닌지를 판별한 후 persist 또는 merge를 수행하는 것을 확인할 수 있다. 새로 만들어진 엔티티의 경우 당연히 DB 또는 persistence context에 없기때문에 존재하는지 확인하지 않고 바로 persist 할 수 있는 것이다.

 

하지만 일반적으로 엔티티를 정의할때 isNew라는 메소드가 없기 때문에 save를 수행할때마다 merge를 하게 된다. 즉, 무조건 DB에 해당 엔티티가 존재하는지 확인한다. 그리고 이런 동작은 새로운 엔티티임에도 불필요한 SELECT문이 발생하면서 성능 이슈의 원인이 된다.

isNew 추가

@Entity
public class Article extends AuditingFields implements Persistable<Long> {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    ...

    @Override
    public boolean isNew() {
        return this.createdAt == null;
    }
}

위 예시 코드처럼 Persistable의 isNew를 Override하면 되는데 간단하게 createdAt(생성일자)가 null일 경우 새로운 엔티티(true)라고 판단하게 했다.

@DynamicUpdate를 사용하여 Update시 변경 부분만 반영

일반적으로 update를 수행하면 모든 필드에 대해 쿼리가 적용된다. 예를 들어 article(게시글)이라는 엔티티의 제목(title)만 update하더라도 그 외 내용(content)도 같이 update가 일어난다.

update article set title=?, content=? where id=?

필드가 많을 경우 이렇게 모든 필드가 update되면 부담이 될 수 있기때문에 @DynamicUpdate를 사용하여 간단하게 변경 부분만 update할 수 있도록 반영할 수 있다.

@Entity
@DynamicUpdate
public class Article extends AuditingFields implements Persistable<Long> {
    ...
}

Delete는?

save 뿐만 아니라 delete를 수행할 때도 SELECT문이 추가로 발생한다. 하지만 delete는 DB에 해당 엔티티가 존재해야 수행할 수 있기때문에 SELECT문으로 확인하는 것은 당연하다. (내부적으로도 수정이 어렵다.)

 

[reference]

https://hermeslog.tistory.com/693

https://jojoldu.tistory.com/415

https://tech.junhabaek.net/hibernate-jpa-entitymanager-%ED%95%B5%EC%8B%AC-%EA%B8%B0%EB%8A%A5-%EC%A0%95%EB%A6%AC-3d0d9ff439a2

728x90