MyBatis + PostgreSQL ? 연산자 충돌 이슈

hs·2026년 3월 31일

문제 상황

최근 팀원분이 MyBatis 쿼리를 수정한 이후 파라미터가 하나씩 밀려서 바인딩되는 현상이 발생한다며 이슈를 공유해주셨습니다.

백엔드에서 디버깅을 해보았을 때 DTO에는 값이 정상적으로 들어가 있었지만, 실제 MyBatis가 실행하는 쿼리를 확인해 보니 아래와 같은 에러가 발생하고 있었습니다.

org.postgresql.util.PSQLException: No value specified for parameter 1

원인분석

에러의 원인을 찾기 위해 새로 추가된 쿼리를 하나씩 제거해 가며 범위를 좁혀 나갔습니다. 그 결과 특정 쿼리를 제거했을 때 에러가 사라지는 것을 확인했고, 해당 쿼리를 자세히 살펴보니 ? 연산자가 포함되어 있었습니다

[작성한 쿼리 예시]

<select id="findData" resultType="map">
    SELECT * FROM my_table 
    WHERE json_column ? 'target_key'
</select>

원인을 찾아보니  ? 연산자의 의미가 각 레이어마다 달라 충돌이 발생해서라고 하였습니다.

레이어? 연산자의 의미
PostgreSQLjsonb 타입에서 해당 키가 존재하는지 확인하는 연산자
JDBC (PreparedStatement)동적 파라미터 바인딩을 위한 예약어

MyBatis는 JDBC 위에서 동작하기 때문에 쿼리 내의 ?를 보는 순간 "파라미터가 바인딩되어야 하는 자리"로 인식합니다. 하지만 실제로는 파라미터를 넘긴 것이 아니라 PostgreSQL의 연산자를 작성한 것이었기 때문에, 매핑할 파라미터 개수가 맞지 않는다는 에러가 발생한 것입니다.

해결 방법

1. PostgreSQL 내장 함수로 대체 (권장)

가장 깔끔하고 근본적인 해결책이라고 합니다. 기호 형태의 연산자 대신 동일한 기능을 하는 PostgreSQL 내장 함수를 사용하면, JDBC 드라이버와의 충돌을 원천적으로 차단할 수 있고 쿼리의 가독성도 좋아집니다.

-- Before
WHERE json_column ? 'target_key'

-- After
WHERE jsonb_exists(json_column, 'target_key')

2. 이스케이프(Escape) 처리

내장 함수를 사용하기 어렵거나 ? 연산자를 반드시 유지해야 하는 상황이라면, JDBC가 파라미터로 인식하지 못하도록 이스케이프 처리를 할 수 있습니다. PostgreSQL JDBC 드라이버에서는 ?를 연속으로 두 번(??) 작성하면 하나의 ?로 해석합니다.

WHERE json_column ? 'target_key'

WHERE json_column ?? 'target_key'

마치며

프레임워크나 드라이버가 내부적으로 사용하는 예약어와 데이터베이스의 고유 문법이 겹칠 때 이와 같은 오류가 발생할 수 있습니다. 겉으로 드러난 증상만 보면 단순한 '파라미터 개수 불일치'처럼 보여서 디버깅 방향을 잡기 어려울 수 있지만, 실제 원인은 레이어 간의 기호 해석 차이에 있었습니다. 쿼리를 작성할 때는 사용 중인 ORM이나 SQL Mapper가 해당 문법을 어떻게 해석할지 한 번쯤 더 고민해 보는 것이 중요할 것 같습니다.

profile
sh

0개의 댓글