[JPA] Entity Lifecycle과 Dirty Checking
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(영속)
화된 엔티티를 생성하여 반환한다.
기본 동작

merge
메소드를 호출하면member
엔티티의 id가 있는지 확인한다. (SELECT문 실행)Persistence Context
에 id가 있다면4.
을 수행한다. 그렇지 않다면DB
에 해당 id의 엔티티를 요청한다.- 응답받은 엔티티를
Persistence Context
에 저장한다. merge
한 엔티티를Persistence Context
에 반영한다.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