2024. 11. 11. 11:46ㆍ내일배움캠프/Spring Plus
'Level 1-1' 의 요구사항을 반영한 내용을 기록한 포스팅이다. 어떠한 생각과 과정을 통해 요구사항을 반영했는지 알 수 있도록 작성해 보았다.
0. 요구사항
현재 'API(POST /todos)' 를 요청할 경우 아래와 같은 에러가 발생하고 있다.
jakarta.servlet.ServletException: Request processing failed: org.springframework.orm.jpa.JpaSystemException: could not execute statement [Connection is read-only. Queries leading to data modification are not allowed] [insert into todos (contents,created_at,modified_at,title,user_id,weather) values (?,?,?,?,?,?)]
에러를 해결해 정상적으로 API 요청이 수행될 수 있도록 코드를 수정하자.
1. 문제 원인 파악
프로젝트를 살펴보니 'org.emaple.export.domain.todo.service.TodoService' 클래스에 아래와 같이 '@Transactional(readOnly = ture)' 가 적용되고 있는 걸 확인 할 수 있었다.
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class TodoService {
...
}
'springframework' 라이브러리의 '@Transactional' 에 대한 docs 를 살펴보면 아래와 같은 내용을 확인할 수 있다.
'@Transactional' 의 옵션 중 'readOnly()' 에 대한 설명이 되어 있는 부분인데, 해석하면 아래와 같다.
트랜잭션이 실제로 읽기 전용인 경우 true로 설정되어 런타임에 해당 최적화가 가능하도록 설정할 수 있는 boolean 플래그입니다. 기본값은 거짓입니다.
즉, 해당 옵션을 'true' 로 설정한다는 것은 해당 트랜잭션을 '읽기 전용' 으로 설정한 것이다. 그래서 '쓰기(= DB 에 데이터 반영)' 를 해야하는 'TodoService.saveTodo()' 의 경우 해당 메서드가 포함된 클래스에 '@Transactional(readOnly = true)' 가 지정되어 있고 해당 메서드는 따로 지정한 것이 없어서 클래스의 설정을 그대로 적용받아 DB 에 새로운 'todo' 를 반영할 수 없다. 그래서 예외가 발생한 것으로 파악했다.
2. 문제 해결
당장 떠오른 해결 방식은 3개였다.
- TodoService 에 지정된 '@Transactional' 의 'readOnly' 옵션을 'false' 로 수정 또는 옵션 제거
- TodoService 에 지정된 '@Transactional' 삭제 후 'saveTodo()' 에 '@Transactional' 지정
- TodoService 에 '@Transactional(readOnly = true)' 유지, 'saveTodo()' 에 '@Transactional' 지정
결국 'saveTodo()' 가 '읽기 전용' 이 아닌 '읽기/쓰기' 가 가능해야 하므로 위와 같이 3가지의 해결 방식을 떠올릴 수 있었는데, 고민후 (3) 의 방식으로 해결하기로 하였다. (3) 의 방식을 선택한 이유는 '내가' 현재 프로젝트를 모두(완벽히) 파악하고 있는 것이 아니기 때문, 분명 해당 프로젝트를 작성한 개발자는 나름의 방식을 통해 코드를 작성했을 것이고 '모든 의도' 를 파악하지 못한 나로써는 최대한 현재 상태를 유지하며 문제를 해결해야겠다는 생각이 들었다. 실무 또는 협업 인원과 소통이 가능하다면 소통을 통해 다른 방식을 사용하겠지만 '개인과제' 특성상 주어진 프로젝트를 수정하는데 (3) 의 방법이 제일 괜찮지 않나 라는 생각을 한 것도 있다.
그래서 아래와 같이 'TodoService.saveTodo()' 에 '@Transactional' 을 지정해 주었다.
@Transactional
public TodoSaveResponse saveTodo(AuthUser authUser, TodoSaveRequest todoSaveRequest) {
User user = User.fromAuthUser(authUser);
String weather = weatherClient.getTodayWeather();
Todo newTodo = new Todo(
todoSaveRequest.getTitle(),
todoSaveRequest.getContents(),
weather,
user
);
Todo savedTodo = todoRepository.save(newTodo);
return new TodoSaveResponse(
savedTodo.getId(),
savedTodo.getTitle(),
savedTodo.getContents(),
weather,
new UserResponse(user.getId(), user.getEmail())
);
}
'@Transactional' 은 '클래스 메서드, 클래스, 인터페이스 메서드' 순으로 높은 우선순위를 가지고 있기에 (3) 의 방식을 활용할 수 있었다. 이렇게 되면 'TodoService' 클래스에서 'saveTodo()' 를 제외한 나머지 메서드들은 기존과 같이 '읽기 전용' 옵션을 그대로 받으면서 'saveTodo()' 는 '읽기/쓰기' 가 가능해져 DB 에 새로운 일정에 대한 정보를 반영할 수 있다.
실제로 Postman 을 통해 API 를 요청해보니 아래와 같이 새로운 일정에 대한 정보가 DB 에 에러 없이 잘 반영된 것을 확인할 수 있었다.
'내일배움캠프 > Spring Plus' 카테고리의 다른 글
[Spring Plus] Level 1-5 요구사항 반영 (0) | 2024.11.13 |
---|---|
[Spring Plus] Level 1-4 요구사항 반영 (0) | 2024.11.13 |
[Spring Plus] Level 1-3 요구사항 반영 (0) | 2024.11.12 |
[Spring Plus] Level 1-2 요구사항 반영 (0) | 2024.11.12 |
[Spring Plus] 5분 기록 테이블 (0) | 2024.11.11 |