문제.
분명 DB에 있는 칼럼 값을 스프링에서 읽어 오지 못하고 null이 발생하는 문제
문제
개발을 하다 문제가 생겼다.
예를 들어,
유저테이블에
id가 1인 삼록 row가 있다고 하자 이때 주소테이블에 '부산시 연제구~~' 라는 값이 있다.
스프링에서 select address from user where = #{id}인 마이바티스 쿼리를 날리는데 id가 1인 변수를 전달해도
계속해서 null값이 뜨는 문제였다.
DB에서 sql쿼리를 직접 날리면 결과가 조회되는데 왜 스프링에서 가져오지 못할까?
변수명이 틀린 것도 아니고 오타가 있는 것도 아니고 스프링에서 왜 null값이 조회되는지 처음엔 답답했다.
원인 1. @Transactional
문제의 원인은 트랜잭션이었다.
먼저 전체적인 조회 구조를 살펴볼 필요가 있다.
예를들어, 전체적인 구조는 클라이언트에서 어떤 버튼을 누르면
스프링에서 다른 애플리케이션(ex.파이썬)으로 하여금 유저 테이블에 주소를 넣는다.
그런다음 즉시 스프링에서 그 주소를 가지고 오는 구조였다.
그리고 이러한 로직을 실행하는 스프링 메서드는 @Transactional 어노테이션이 붙어있었다.
그리고 연결되어있는 DB는 mySQL이다.
원인 2. 트랜잭션 격리 수준
mySQL의 기본 트랜잭션 격리 수준은 REPEATABLE READ다.
(참고로 Oracle, PostgreSQL의 격리수준은 READ COMMITED)
(*트랜잭션의 격리수준에 대한 설명은 https://gotopm.tistory.com/55 의 6번 항목 참고)
@Transactional어노테이션이 붙어있으므로,
스프링이 처음 메서드를 실행할 때 트랜잭션이 시작된다.
트랜잭션이 시작되면 MySQL은 스냅샷을 생성한다.
이 스냅샷을 생성할 때 id가 1인 삼록의 row에는 바로 주소칼럼이 비워져있다.
그리고 스프링에서 다른 애플리케이션(ex.파이썬)으로 하여금 유저 테이블에 주소를 넣어도
이 트랜잭션안에서는 처음 스냅샷을 보고 판단하기에 주소가 없는 null값으로 판단한 것이다.
만약 연결되어있는 DB가 Oracle이나 PostgreSQL이었다면 기본 트랜잭션 격리수준이 READ COMMITED로 설정되어있었을 것이고 이 경우에는 커밋이 완료된 데이터는 바로 읽을 수 있으므로 파이썬이 유저테이블에 주소를 인서트 커밋을 완료하자마자 스프링에서는 정상적으로 값을 읽어올 수 있었을 것이다.
해결
이 경우에는 2가지 방법이 있다.
트랜잭션이 없는 상태에서 조회오도록 해야하거나 MySQL 격리 수준을 READ COMMITED로 변경이 필요하다.
그래서 간단하게 트랜잭션이 없는 상태에서 조회해오는 방법을 선택했다.
바로 @Transactional 어노테이션을 제거하거나 @Transactional(propagation = Propagation.NOT_SUPPORTED) 옵션을 사용하면 트랜잭션이 없는 상태에서 조회해올 수 있다.
*Transactional어노테이션을 제거한것과 Propagation.NOT_SUPPORTED 옵션을 준 것의 차이는?

즉 향후에 트랜잭션을 실행시키는 다른 메서드(상위 메서드) 내부에서 다시 이 문제가 일으킨 메서드(하위 메서드)를 실행시킨다면 propagation = NOT_SUPPORTED 옵션을 붙여놓으면 이 메서드는 상위 트랜잭션을 중단 시키고 트랜잭션이 없는 상태로 실행된다.
스프링 트랜잭션이 REPEATABLE READ 스냅샷을 고정해버려서,
트랜잭션 시작 이후에 외부에서 INSERT한 row가 현재 트랜잭션에서는 절대 보이지 않았던 것이 문제의 본질이었다.
'트러블슈팅' 카테고리의 다른 글
| QueryDSL DTO 내부 리스트에 하나의 결과만 조회 되는 문제 (4) | 2025.08.31 |
|---|---|
| 정규화 vs 반정규화 (1) | 2025.07.19 |