TIL

[231221] update 시 Timestamp 값으로 null이 반환

진진리 2023. 12. 21. 22:14
728x90

문제 상황

게시글을 수정하는 API를 구현하는 과정에서 기존의 방식과 달리

repository.save() 매서드를 사용하여 update한 내용을 반영하고자 하였다.

 

이때 createdAt과 modifiedAt 필드의 값이 null로 반환되었다.

 

PostServiceImpl 기존 코드 :: updatePost()

@Override
@Transactional
public PostUpdateRes updatePost(Long postId, PostUpdateReq req, User user) {
    Post post = postReadService.getPostEntity(postId);

    PostValidator.checkPostAuthor(post, user);

    Post savedPost = postRepository.save(Post.builder()
        .postId(postId)
        .title(req.getTitle())
        .content(req.getContent())
        .user(user)
        .build()
    );

    return PostServiceMapper.INSTANCE.toPostUpdateRes(savedPost);
}

 

 

  • 처음 데이터 조회했을 때 1차 캐싱
  • save 할때 둘다 null 로 변경 (빌더에 createdAt, modifiedAt 값이 없으므로)
  • @LastModifiedDate 는 DB에 변경사항이 일어났을 때의 시간이 저장되는 구조이므로 아직 트렌젝션이 끝나지 않은 상태에서 조회하면 캐싱된 데이터가 조회되어 둘다 null 로 노출


주의할 점:: TimeStamp의 @CreatedDate, @LastModifiedDate

@CreatedDate와 @LastModifiedDate 애너테이션은 엔티티가 저장된 시점, 수정된 시점을 필드에 기록한다.

 

CreatePost()와의 비교

@Override
public PostCreateRes createPost(PostCreateReq req, User user) {
    Post savePost = postRepository.save(Post.builder()
        .title(req.getTitle())
        .content(req.getContent())
        .user(user)
        .build()
    );

    return PostServiceMapper.INSTANCE.toPostCreateRes(savePost);
}

 

updatePost와 코드가 거의 유사함에도 불구하고 createPost의 반환값은 잘 반환되었다.

1차 캐시로 인해 해당 문제다고 생각해서 @Transactional을 제거해보았다.

 

UpdatePost() 1차 수정 코드

@Override
public PostUpdateRes updatePost(Long postId, PostUpdateReq req, User user) {
    Post post = postReadService.getPostEntity(postId);

    PostValidator.checkPostAuthor(post, user);

    Post savePost = postRepository.save(Post.builder()
        .postId(postId)
        .title(req.getTitle())
        .content(req.getContent())
        .user(user)
        .build()
    );

    return PostServiceMapper.INSTANCE.toPostUpdateRes(savePost);
}

 

그러자 createdAt은 변함없이 null값이었지만 modifiedAt에는 값이 제대로 반환되었다.

 

  • 데이터 조회, save(), 다시 조회 과정이 각각의 트랜잭션으로 분리되어 실행되었으므로 save() 단계에서 modifiedAt 값이 변경되었을 수 있음
  • 하지만 트랜잭션이 끝났더라도 영속성 컨텍스트가 살아 있음
  • update 쿼리가 실행되었으므로 modifiedAt 에만 데이터가 변경되고 createdAt 은 null

UpdatePost() 2차 수정 코드

이때 application.yml 파일의 jpa.open-in-view: false로 설정해준 뒤

저장한 게시글 엔티티를 다시 조회해주었더니 값이 둘 다 제대로 반환되었다.

 

@Override
public PostUpdateRes updatePost(Long postId, PostUpdateReq req, User user) {
    Post post = postReadService.getPostEntity(postId);

    PostValidator.checkPostAuthor(post, user);

    postRepository.save(Post.builder()
        .postId(postId)
        .title(req.getTitle())
        .content(req.getContent())
        .user(user)
        .build()
    );

    return PostServiceMapper.INSTANCE.toPostUpdateRes(postReadService.getPostEntity(postId));
}

 

  • open-in-view 설정을 false로 하면서 save이후 entity manager가 닫혔기 때문에 해당 엔티티가 DB에 저장됨
  • 해당 엔티티를 다시 조회함으로써 createdAt 값이 들어감

 

application.yml 파일의 jpa.open-in-view: false 설정

  • 기본값: true true일 경우 -  트랜잭션이 종료되어도 entity mananger가 닫히지 않음

  • false일 경우  - 트랜잭션을 종료할 때 entity mananger 가 닫힘

출처: https://velog.io/@dnwlsrla40/JPA-Open-In-View