
2024-07-06T17:49:35.020+09:00 WARN 19220 --- [nio-8080-exec-3] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 0, SQLState: S1009
2024-07-06T17:49:35.020+09:00 ERROR 19220 --- [nio-8080-exec-3] o.h.engine.jdbc.spi.SqlExceptionHelper : Connection is read-only. Queries leading to data modification are not allowed
2024-07-06T17:49:35.027+09:00 ERROR 19220 --- [nio-8080-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [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 post (content,created_at,created_by,title,updated_at,updated_by) values (?,?,?,?,?,?)]] with root cause
java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:130) ~[mysql-connector-j-8.3.0.jar:8.3.0]
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:98) ~[mysql-connector-j-8.3.0.jar:8.3.0]
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:90) ~[mysql-connector-j-8.3.0.jar:8.3.0]
...
@PostMapping("/posts")절을 이용하여 POST API 를 호출하였는데,
위와 같이Connection is read-only...와 같은 오류가 발생하였습니다.
이는 간단하게 설명하면, @Transactional 어노테이션이 적용된 메서드나 클래스에 readOnly 옵션이 설정되어 있는데, 조작을 시도해서 발생하는 오류입니다.
@RestController
class PostController(
private val postService: PostService,
) {
@PostMapping("/posts")
fun createPost(
@RequestBody postCreateRequest: PostCreateRequest,
): Long {
return postService.createPost(postCreateRequest.toDto())
}
...
}
@Service
@Transactional(readOnly = true)
class PostService(
private val postRepository: PostRepository,
) {
// @Transactional
fun createPost(requestDto: PostCreateRequestDto): Long {
return postRepository.save(requestDto.toEntity()).id
}
...
}
위의 예시에서, /posts의 API가 호출되면, PostService의 createPost 메서드가 호출되는데, PostService 내의 메서드들이 모두 @Transactional 어노테이션이 적용되어 있고, readOnly 옵션도 설정되어 있습니다.
여기서 createPost 메서드에 별다른 옵션 설정이 추가적으로 있지 않았기 때문에, DB를 읽는 용도로만 사용할 수 있게 되었습니다.
하지만, 실제 작동은 save, 즉 데이터를 새로 작성하는 기능을 수행하였기에 위와 같은 오류가 발생하였습니다.
위와 같은 오류를 방지하기 위해, 주석에 적어 놓은
@Transactional어노테이션을 한번 더 적어주어readOnly옵션이 적용된 것을 해제해주어야 합니다.
옵션은 함수단위에 있는게 먼저 적용됩니다@Transactional fun createPost(requestDto: PostCreateRequestDto): Long { return postRepository.save(requestDto.toEntity()).id }위와 같이 적용해주거나, readOnly 옵션을 삭제해 주면서 해당 오류를 해소할 수 있습니다.
그러면, 왜 이 옵션을 적용할까요?
간단하게 짚고 넘어가겠습니다.
성능 최적화
트랜잭션을 읽기 전용으로 설정하면 해당 메서드가 데이터를 읽기만 한다는 것을 DB에 미리 알려주기 때문에, DB 작업 중의 쿼리 및 캐싱을 최적화할 수 있습니다.
그리고 읽기 전용으로 설정하며 데이터 변경이 일어나지 않기 때문에 변경감지를 위한 스냅샷을 저장하는 등의 동작 또한 하지 않아 성능이 저하를 막을 수 있습니다.
데이터 일관성
트랜잭션 자체가 DB 데이터의 일관성과 무결성을 보장하기위해 주로 사용되는데, 트랜잭션을 readOnly로 설정하면 의도하지 않은 데이터 조작과 수정을 예방할 수 있어 일관성을 위반할 가능성을 낮출 수 있습니다.
가독성 향상
코드를 작성하는 개발자는 @Transactional(readOnly=true)이 설정된 메서드가 DB에서 데이터를 읽기만 한다는 것을 명확하게 확인할 수 있습니다. 이로 인해 코드의 가독성이 향상이 됩니다.
다만 주의할 점은, @Transactional(readOnly = true)은 조회 서비스와 같이 DB에서 데이터를 읽기만 하는 서비스 메서드에 적용을 해야 합니다.
데이터 처리 메서드에 위 어노테이션이 적용되면 이 포스트에서 다루었던 오류 메세지가 발생할 것이고, 트랜잭션 오류로 감지하여 서비스가 중단될 것입니다.
https://velog.io/@jhbae0420/TransactionalreadOnly-true를-사용하는-이유와-주의할점