현재 개발하고 있는 프로젝트에서 Review
라는 테이블이 있는데, 리뷰의 생성 및 마지막 수정시간을 기록하기 위해 @CreatedDate
와 @LastModifiedDate
어노테이션을 사용하는 필드를 만들었다.
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseTimeEntity {
@CreatedDate
private LocalDateTime createdDate;
@LastModifiedDate
private LocalDateTime modifiedDate;
}
리뷰의 좋아요/싫어요를 업데이트하는 요청을 받게 되면 관련 테이블 작업을 하고나서 Review
테이블의 likeCnt와 dislikeCnt 필드의 값을 수정한다. 이 때 테이블이 업데이트 되면서 @LastModifiedDate
필드도 같이 갱신되었다.
그러나 리뷰의 마지막 수정시간은 제목, 본문, 평점과 같은 정보들이 수정되고 좋아요/싫어요 카운트는 여기에서 제외되길 원했다. 그래서 방법들을 찾아보았다.
아예 likeCnt와 dislikeCnt 필드를 다른 테이블로 분리하여 사용하는 방법이 있다. 하지만 데이터 수정, 조회 시 소요되는 리소스가 더 많다고 판단되어 후보에서 제외했다.
JPA에는 해당 엔티티의 데이터가 업데이트되기 직전에 실행되는 메서드에 사용하는 @PreUpdate라는 어노테이션이 있다.
@Entity
public class Review extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long reviewId;
@ManyToOne
@JoinColumn(name = "user_id")
private UserAccount userAccount;
@ManyToOne
@JoinColumn(name = "contents_id")
private Contents contents;
@Setter
private String title;
@Setter
private String content;
@Setter
private float score;
private Date created; // 생성시간
private Date modified; // 마지막 수정시간
@Transient
private Review previousState;
@PostLoad
public void setPreviousState() {
previousState = new Review();
// copy fields
}
@PreUpdate
public void preUpdate() {
if (isModified()) {
modified = new Date();
}
}
private boolean isModified() {
boolean modified = false;
if (!title.equals(previousState.title) || !content.equals(previousState.content) || ...) {
modified = true;
}
//check other fields
return modified;
}
}
위와 같이 이전 상태를 저장했다가 엔티티가 업데이트 되면 값을 비교하여 특정 필드가 수정될 때에만 date를 갱신할 수 있다.
이러한 방법은 원하는 특정 필드의 값이 수정될 때에만 마지막 수정 시간을 갱신할 수 있다는 장점이 있지만, 이전 상태를 가지고 있어야 하고 관련된 필드가 늘어날 때마다 isModified() 메소드를 수정해주어야 하는 등의 번거로움이 있다.
@LastModifiedDate
어노테이션은 JPA의 내부 이벤트 처리 메커니즘에 의해 관리된다. 이 어노테이션은 엔티티가 JPA에 의해 관리되는 경우에만 자동으로 업데이트되며, 직접 JPQL(Java Persistence Query Language)을 사용하여 업데이트 쿼리를 실행할 때는 작동하지 않는다. 이러한 점을 이용하여 특정 필드 값을 업데이트해도 @LastModifiedDate
필드가 갱신되지 않도록 할 수 있다.
public interface ReviewRepository extends JpaRepository<Review, Long> {
Page<Review> findAllByContents(Contents contents, Pageable pageable);
// ...
@Modifying
@Query("UPDATE Review SET likeCnt = :likeCnt WHERE reviewId = :reviewId")
void updateLikeCount(@Param("reviewId") Long reviewId, @Param("likeCnt") int likeCnt);
@Modifying
@Query("UPDATE Review SET dislikeCnt = :dislikeCnt WHERE reviewId = :reviewId")
void updateDislikeCount(@Param("reviewId") Long reviewId, @Param("dislikeCnt") int dislikeCnt);
}
// like, dislike count 갱신
reviewRepository.updateLikeCount(review.getReviewId(), reviewLikeService.getLikeCount(review));
reviewRepository.updateDislikeCount(review.getReviewId(), reviewLikeService.getDislikeCount(review));