uswLectureEvaluation/SUWIKI-Spring

강의평가 작성/수정 API : 500 응답 SQL 예외

Closed this issue · 0 comments

@K-Diger 도움 요청드립니다~!
혹시 네이티브 쿼리를 쓰시게 된 맥락을 알 수 있을까요?

김영한님 JPA 책을 보면 비관적 락은 DB의 SELECT ... FOR UPDATE 문으로 수행되고, 도현님 블로그(감사히 보고 있습니다)를 보면 락의 대상은 강의인 것으로 이해했습니다. => 도현님께서 의도하신 쿼리문(SELECT ... lecture FOR UPDATE)을 수행시키도록 코드를 수정해봤습니다.

파일 히스토리를 보니 도현님께서 hotfix를 몇번 하셨더라구요.
혹시 아래에서 제가 생각하는 해결방법에 문제가 있는지 궁금합니다..!
없다면 레포지토리 테스트 추가 후 PR 올리겠습니다

상황

  • 12.26 문제 확인

  • 강의 평가 작성시 500 에러 - SQL 예외

    Image_2023-12-26 18_56_41

  • URI
    {{host}}:{{port}}/evaluate-posts?lectureId=1

  • request body

    {
      "lectureName": "english",
      "selectedSemester": "2023-2",
      "professor": "john",
      "satisfaction": 1,
      "learning": 1,
      "honey": 1,
      "team": 1,
      "difficulty": 1,
      "homework": 1,
      "content": "this is sucks. go to hell mr.john!!!!"
    }
  • response

    {
        "exception": "InvalidDataAccessApiUsageException",
        "code": "NO_CATCH_ERROR",
        "message": "Illegal attempt to set lock mode on a native SQL query; nested exception is java.lang.IllegalStateException: Illegal attempt to set lock mode on a native SQL query",
        "status": 500,
        "error": "Internal Server Error"
    }
  • error log

    2023:12:26 19:47:01.653 ERROR --- [http-nio-8080-exec-6] u.s.g.e.GlobalExceptionHandler : 
    code : NO_CATCH_ERROR, message : 
    Illegal attempt to set lock mode on a native SQL query; 
    nested exception is java.lang.IllegalStateException: 
    Illegal attempt to set lock mode on a native SQL query
    

예상되는 에러 지점

  • LectureRepository - findByIdPessimisticWrite 메서드의 Lock

    @Query(value = "SELECT * FROM lecture WHERE id = :id FOR UPDATE", nativeQuery = true)
    @Lock(value = LockModeType.PESSIMISTIC_WRITE)
    Lecture findByIdPessimisticWrite(@Param("id") Long id);

    디버깅 결과로는 해당 메서드가 호출되는 시점에 위 에러 로그와 동일한 에러가 발생합니다.

Lock을 스프링에 적용해본 적은 없어서 더 공부해보면서 이슈 업데이트하겠습니다

해결

💡 native Query 대신 JPQL (Spring Data Jpa) 사용

JPQL ver.

@Query(value = "SELECT l FROM Lecture AS l WHERE l.id = :id")
@Lock(value = LockModeType.PESSIMISTIC_WRITE)
Lecture findByIdPessimisticWrite(@Param("id") Long id);

Data Jpa ver.

@Lock(value = LockModeType.PESSIMISTIC_WRITE)
Optional<Lecture> findLockedLectureById(Long lectureId);

-> 기능 정상 동작. @Lock 을 통해 비관적 락(FOR UPDATE)이 적용되는걸로 보임.

도현님 블로그의 내용까지 종합했을 때, 현재 이 방법이 제일 좋아보입니다.

  • 수행된 쿼리문 (세부 컬럼들은 축소했습니다)
select *
from
    evaluate_post evaluatepo0_ 
where
    evaluatepo0_.id=?

select *
from
    lecture lecture0_ 
where
    lecture0_.id=? for update
          
update
    evaluate_post 
set
    # ...
where
    id=?

update
    lecture 
set
    # ...
where
    id=?

추가로 javax.persistence.lock.timeout 를 사용해서 락의 타임아웃을 설정할 수 있다고 합니다!
비관적 락인 만큼 필요한 설정이라고 생각해요. (솔직히 락 타임아웃이 왜 필요한지 체감은 안 되긴 합니다.ㅋㅋ 수정이 오래 걸리면 오래 걸리는대로 처리해줘야하는거 아닌가 싶기도 하구요)
이 정도까지 갈 일은 없겠지만 mysql 디폴트 타임아웃 시간이 너무 기네요.. 31536초..

image

참고