post가 delete될 때, 그와 관련되어 있는 comment와 like도 soft delete처리를 추가해 주었다.
이 과정에서 생겼던 Transactional 관련 이슈와
그로 인해 jpql method를 줄이고 jpa를 사용하기 위해 리펙토링 한 과정을 적어보았다...
1. @Transactional
jpa의 경우 더티체킹을 지원한다. 이는 상태 변경에 대한 검사라 생각하면 된다.
JPA로 엔티티를 조회하게 되면, 엔티티 조회 상태 그대로 스냅샷을 만들게 되고, 엔티티를 수정하여 트랜젝션을 커밋 할 때, flush가 일어나게 되는데, 그 때 초기 조회 상태의 스냅샷과 비교하여 변화가 발생하면, update query를 날리게 된다. 이로 인해 따로 save를 해주지 않아도 update query가 실행되는 것이다.
다만, 이러한 더티체킹은 영속성 컨텍스트에 등록된 엔티티에만 적용이 된다.
다른 설정을 따로 하지 않고 편하게 더티체킹이 일어나게 하도록 @Transactional을 사용하는 것으로 추정 된다.
해당 annotation을 사용하게 되면, 해당 범위 내 method를 호출 할 때 트렌잭션이 시작할 때 영속성 컨텍스트가 생성된다. 해당 method의 로직이 끗나면 트렌잭션이 커밋되면서 위의 문단의 과정이 실행되는 것이다.
더티체킹이 일어나는 시점은
- 트랜젝션이 커밋될때
- flush가 일어날 때
- JPQL이 사용할 때
일어난다. 사실 트랜젝션이 커밋될때는 flush가 일어나므로 밑의 조건과 동일하다 볼 수 있다.
이 때, JPQL에 대해 좀 더 조사해 보았는데,
JPQL은 영속성 컨텍스트의 데이터 변화에 기여하지 않는다. 즉 영속성 컨텍스트를 거치지 않고, db에 직접 query하여 결과를 가져온다고 한다.
따라서 JPQL이 실행하기 전에 flush가 자동으로 실행되며 영속성 컨텍스트와 db의 일관성을 확보한다.
(flush 란 영속성 컨텍스트의 변경 내용을 db에 저장하는 것이다.)
다만, JPQL 쿼리가 나가면서 수정이 일어나더라도, 영속성 컨텍스트의 데이터는 변경시키지 않았기 때문에, 수정 되기 전의 상태가 저장되어 있다. 따라서
- flush 와 save를 통해 영속성 컨텍스트의 내용을 db에 반영하고, 영속성 컨텍스트의 초기화
- @Modifying(clearAutomatically = true)를 이용하여 query 실행 후, 영속성 컨텍스트를 초기화
같은 방법을 통해 db와 영속성 컨텍스트 간의 동기화를 하여, 후에 같은 트랜젝션 안에 있는 실행할 JPA query 등에 영향이 없도록 하는 것이 좋아 보인다.
이러한 JPQL과 JPA의 구동 차이 때문에, 최대한 JPA method만 사용하도록 하였다.
간단한 query는 자바 안에서 처리하는 것이 좋다는 멘토님의 조언도 위의 이유 때문으로 추정된다.
다만, 여러 JPA query와 JPQL query를 혼용하여 사용하며 @Transactional을 붙여주지 않을 때 error가 발생하였는데,
왜 error가 발생하였으며 이 때 왜 JwtTokenFilter Error가 갑자기 발생했는지 원인을 밝혀내진 못했다.
2023-01-05 16:05:04.184 ERROR 8700 --- [nio-8080-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause
위의 error가 뜨고 나서,
java.lang.NullPointerException: null
Controller에서 Authentication을 불러오는 데서 발생하고, JwtTokenFilter에서 발생했다고 떴다.
따라서 일단 db에 내용이 변경되는 로직이 있는 service method에는 모두 @Transactional annotation을 붙여 주었다.
<Reference>
1. JPQL 관련
https://cobbybb.tistory.com/11
https://wwlee94.github.io/category/blog/spring-transactional-precautions/
https://joont92.github.io/jpa/JPQL/
2. 영속성
https://ultrakain.gitbooks.io/jpa/content/chapter3/chapter3.2.html
3. 더티체킹
https://jojoldu.tistory.com/415
https://interconnection.tistory.com/121
4. flush
https://gmlwjd9405.github.io/2019/08/07/what-is-flush.html
2. JPQL query -> JPA 사용
1번 내용 관련 공부를 하면서, 되도록 JPA를 사용하는 것이 편리하고 문제가 덜 생기겠구나를 깨닳았다.
다소 복잡한 쿼리의 경우에만 JPQL 로 작성하고, 간단한 변경 로직이라면 JPA로 설정하는 것이 좋다는 멘토님의 조언이 어떤 뜻인지 살짝은 느낌이 오는 듯 하다.
따라서 기존의 deletedAt에 시간을 update하는 JPQL 쿼리를 java 내부 로직으로 변경해 주었다.
아래는 기존의 JPQL query이다.
@Query("update Like l set l.deletedAt = current_timestamp where l.deletedAt is null and l.post = :post")
@Modifying(clearAutomatically = true)
void deleteAllByPost(@Param("post") Post post);
Like와 manyToOne 관계를 맺은 post를 기준으로 검색하여, 해당 post와 관계를 갖고 있고, deletedAt 이 null인 경우, 해당 post가 delete 될 때, soft delete를 하기 위한 query이다.
단순히 JpaRepository의 deleteAllByPost를 이용하지 않고 이 query를 따로 짜준 이유는, 이미 좋아요 취소로 인해 deletedAt이 설정되어 있는 것들의 deletedAt의 시간이 갱신되는 것을 원하지 않았기 때문이다.
다만, 생각해보니 다소 간단힌 로직이라, java 내부 로직으로 분리할 수 있으면 분리하는것이 좋다 생각이 들었다.
따라서 JpaRepository에 아래의 method를 추가해 주고,
Optional<List<Like>> findByPost(Post post);
PostService에서 아래의 로직으로 deletedAt이 null인 like만 뽑아 deleteAt을 JpaRepository의 delete를 이용하여 수정하였다. (Like Entity에서 annotation으로 soft delete를 설정한 상태이다.)
@SQLDelete(sql = "UPDATE likes SET deleted_at = current_timestamp WHERE id = ?")
Optional<List<Like>> likes = likeRepository.findByPost(post);
for (int i = 0; i < likes.get().size(); i++) {
if (likes.get().get(i).getDeletedAt() == null) {
likeRepository.delete(likes.get().get(i));
}
}
@Transactional annotation은 단지 error가 났을 때 db에 반영되는 것을 롤백하기 위한 annotation이라고만 알고 있었는데, 이번 error로 인해 어떤 기능을 하는지 원초적으로 살펴볼 기회가 된 것 같다.
JPA와 JPQL의 차이점도 알게 되었으며, 왜 JPA가 편리한지 생각 할 수 있는 계기가 된 듯 하다.
아직은 단순 로직만 짜기 바쁜 상태밖에 안되는 것을 깨닳았고, 정말 공부가 많이 필요하다 생각이 들었다.
<전체 Reference>
https://cobbybb.tistory.com/11
https://wwlee94.github.io/category/blog/spring-transactional-precautions/
https://joont92.github.io/jpa/JPQL/
https://ultrakain.gitbooks.io/jpa/content/chapter3/chapter3.2.html
https://velog.io/@seongwon97/Spring-Boot-%EC%98%81%EC%86%8D%EC%84%B1-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8Persistence-Context
https://velog.io/@neptunes032/JPA-%EC%98%81%EC%86%8D%EC%84%B1-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8%EB%9E%80
https://jojoldu.tistory.com/415
https://interconnection.tistory.com/121
https://gmlwjd9405.github.io/2019/08/07/what-is-flush.html
'프로젝트 > 멋사 개인 프로젝트 (mutsa-SNS)' 카테고리의 다른 글
[22] mutsa-SNS-2 4일차 - 마이 피드 기능 test code (0) | 2023.01.09 |
---|---|
[21] mutsa-SNS-2 3일차 - (2) 마이 피드 기능 구현 (0) | 2023.01.05 |
[19] mutsa-SNS-2 2일차 - (2) 좋아요 기능 test code (0) | 2023.01.04 |
[18] mutsa-SNS-2 2일차 - (1) 좋아요 기능 구현 (soft delete 복구) (0) | 2023.01.04 |
[17] mutsa-SNS-2 1일차 - (2) 댓글 기능 test code (0) | 2023.01.03 |