[SpringBoot] Transactional 어노테이션 오류, Connection is read-only

zero zoo·2024년 7월 6일

SPRING 정리

목록 보기
2/3
post-thumbnail

문제

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가 호출되면, PostServicecreatePost 메서드가 호출되는데, PostService 내의 메서드들이 모두 @Transactional 어노테이션이 적용되어 있고, readOnly 옵션도 설정되어 있습니다.

여기서 createPost 메서드에 별다른 옵션 설정이 추가적으로 있지 않았기 때문에, DB를 읽는 용도로만 사용할 수 있게 되었습니다.

하지만, 실제 작동은 save, 즉 데이터를 새로 작성하는 기능을 수행하였기에 위와 같은 오류가 발생하였습니다.

위와 같은 오류를 방지하기 위해, 주석에 적어 놓은 @Transactional 어노테이션을 한번 더 적어주어 readOnly 옵션이 적용된 것을 해제해주어야 합니다.
옵션은 함수단위에 있는게 먼저 적용됩니다

@Transactional
fun createPost(requestDto: PostCreateRequestDto): Long {
    return postRepository.save(requestDto.toEntity()).id
}

위와 같이 적용해주거나, readOnly 옵션을 삭제해 주면서 해당 오류를 해소할 수 있습니다.


@Transactional(readOnly = true)

그러면, 왜 이 옵션을 적용할까요?
간단하게 짚고 넘어가겠습니다.

  1. 성능 최적화
    트랜잭션을 읽기 전용으로 설정하면 해당 메서드가 데이터를 읽기만 한다는 것을 DB에 미리 알려주기 때문에, DB 작업 중의 쿼리 및 캐싱을 최적화할 수 있습니다.
    그리고 읽기 전용으로 설정하며 데이터 변경이 일어나지 않기 때문에 변경감지를 위한 스냅샷을 저장하는 등의 동작 또한 하지 않아 성능이 저하를 막을 수 있습니다.

  2. 데이터 일관성
    트랜잭션 자체가 DB 데이터의 일관성과 무결성을 보장하기위해 주로 사용되는데, 트랜잭션을 readOnly로 설정하면 의도하지 않은 데이터 조작과 수정을 예방할 수 있어 일관성을 위반할 가능성을 낮출 수 있습니다.

  3. 가독성 향상
    코드를 작성하는 개발자는 @Transactional(readOnly=true)이 설정된 메서드가 DB에서 데이터를 읽기만 한다는 것을 명확하게 확인할 수 있습니다. 이로 인해 코드의 가독성이 향상이 됩니다.

다만 주의할 점은, @Transactional(readOnly = true)은 조회 서비스와 같이 DB에서 데이터를 읽기만 하는 서비스 메서드에 적용을 해야 합니다.

데이터 처리 메서드에 위 어노테이션이 적용되면 이 포스트에서 다루었던 오류 메세지가 발생할 것이고, 트랜잭션 오류로 감지하여 서비스가 중단될 것입니다.


출처

https://velog.io/@jhbae0420/TransactionalreadOnly-true를-사용하는-이유와-주의할점

profile
한방향으로 지그재그

0개의 댓글