Troubleshooting

[MyBatis/Oracle] ORA-17004: 열 유형이 부적합합니다 (TypeException) 해결 방법

sowith 2026. 3. 4. 03:22

MyBatis를 사용하여 Oracle에 데이터를 삽입할 때 특정 파라미터 값이 null인 경우 ORA-17004 에러가 발생하는 경우가 있다.

특히 전역 설정을 마쳤음에도 불구하고 에러가 지속되었다.

 

1. 문제 상황

일반 회원가입 기능을 테스트하던 중 소셜 로그인 관련 필드가 null로 넘어오면서 다음과 같은 예외가 발생했다.

Caused by: org.apache.ibatis.type.TypeException: Error setting null for parameter #6 with JdbcType OTHER . 
Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. 
Cause: java.sql.SQLException: ORA-17004: 열 유형이 부적합합니다.: 1111

 

로그를 확인해 보니 6번째 파라미터(#6)에서 문제가 발생했다. 필드 순서를 확인해 본 결과 6번째는 provider였으며 그 뒤의 providerId 역시 null인 상태였다.

 

2. Oracle의 Null 처리

Oracle은 다른 DB와 달리 null 데이터가 들어올 때 해당 컬럼의 데이터 타입이 무엇인지 명확하게 명시해 줄 것을 요구한다.

  • MyBatis는 기본적으로 null 값을 보낼 때 JdbcType을 OTHER로 설정하여 던진다.
  • Oracle 드라이버는 OTHER라는 타입을 이해하지 못하고 ORA-17004 (부적합한 열 유형) 에러를 발생시킨다.
  • 특히 EnumTypeHandler를 사용할 경우 값이 null이면 핸들러 자체적으로 타입을 결정하지 못해 이 문제가 더욱 빈번하게 발생한다.

 

3. 전역 설정을 통한 해결 시도

보통 application.yml에 아래와 같은 설정을 추가하면 null 처리가 가능하다고 알려져 있다.

 
mybatis:
  configuration:
    map-underscore-to-camel-case: true
    default-enum-type-handler: org.apache.ibatis.type.EnumTypeHandler
    # null이 들어올 경우 타입을 NULL로 처리하도록 설정
    jdbc-type-for-null: "NULL"

 

하지만 내 프로젝트 코드에서는 이 설정이 무용지물이었다. 이유는 Mapper에서 typeHandler를 직접 명시했기 때문이다.

  • MyBatis는 매퍼의 #{...} 안에 typeHandler가 적혀 있으면 전역 설정보다 이 설정을 우선시한다.
  • EnumTypeHandler는 null을 넘겨받았을 때 jdbcType 정보가 없으면 Oracle 드라이버에 OTHER 타입을 던진다.
  • Oracle은 null 값이라 하더라도 해당 데이터가 어떤 타입(VARCHAR2, NUMBER 등)인지 명확히 알지 못하면 ORA-17004 에러를 발생시킨다.

 

4. jdbcType 타입 지정해주기

에러 로그가 가리킨 6번째 파라미터인 provider뿐만 아니라 null이 들어올 가능성이 있는 providerId까지 모두 jdbcType을 명시해야 문제가 완전히 해결된다. 일반 문자열 필드라도 Oracle 환경에서는 null 처리를 위해 jdbcType을 적어주는 것이 안전하다.

수정 전 

<insert id="insertUser">
    INSERT INTO USERS (USER_ID, EMAIL, USERNAME, PASSWORD, ROLE, PROVIDER, PROVIDER_ID, CREATED_AT)
    VALUES (#{vo.userId}, #{vo.email}, #{vo.username}, #{vo.password},
            #{vo.role, typeHandler=org.apache.ibatis.type.EnumTypeHandler},
            #{vo.provider, typeHandler=org.apache.ibatis.type.EnumTypeHandler}, 
            #{vo.providerId}, SYSTIMESTAMP)
</insert>

 

6번째 파라미터인 provider에 jdbcType이 없으며 providerId 역시 명시되지 않은 상태다.

수정 후 

<insert id="insertUser">
    INSERT INTO USERS (USER_ID, EMAIL, USERNAME, PASSWORD, ROLE, PROVIDER, PROVIDER_ID, CREATED_AT)
    VALUES (
        #{vo.userId}, 
        #{vo.email}, 
        #{vo.username}, 
        #{vo.password, jdbcType=VARCHAR},
        #{vo.role, typeHandler=org.apache.ibatis.type.EnumTypeHandler, jdbcType=VARCHAR},
        #{vo.provider, typeHandler=org.apache.ibatis.type.EnumTypeHandler, jdbcType=VARCHAR}, 
        #{vo.providerId, jdbcType=VARCHAR}, 
        SYSTIMESTAMP
    )
</insert>

 

null이 될 수 있는 모든 필드에 jdbcType=VARCHAR를 세트로 추가했다.

 

5. 왜 전역 설정만으로는 해결되지 않았을까?

이번 트러블 슈팅을 통해 배운 점은 MyBatis와 Oracle 사이의 미묘한 우선순위 규칙을 이해한 것이다. 비슷한 에러를 방지하기 위해 다음 세 가지를 기억해야 한다.

 

① 설정 우선 순위 - 개별 설정이 전역 설정을 덮어쓴다

application.yml에 설정한 jdbc-type-for-null은 MyBatis가 해당 파라미터에 대해 아무런 정보가 없을 때 사용하는 기본값이다. 하지만 매퍼 XML에서 typeHandler를 직접 지정하는 순간 MyBatis는 개발자가 해당 필드의 처리를 완전히 제어하겠다는 신호로 받아들인다. 따라서 전역 설정은 무시되며 핸들러를 명시했다면 jdbcType까지 세트로 적어줘야 안전하다.

 

② EnumTypeHandler

EnumTypeHandler는 Enum과 DB 타입을 매핑해 주지만 전달된 값이 null이면 이야기가 달라진다. 핸들러는 전달받은 데이터가 없으므로 이를 어떤 DB 타입으로 변환해야 할지 스스로 판단하지 못한다. 이때 명시적인 jdbcType 가이드가 없으면 MyBatis는 기본값인 OTHER 타입을 드라이버에 던지고 이는 Oracle의 타입 부적합 에러로 이어진다.

 

③ Oracle 환경에서의 Null

MySQL 같은 관대한 DB와 달리 Oracle은 null 값조차도 명확한 데이터 타입을 요구하는 엄격한 성격을 가졌다. 특히 소셜 로그인 필드(provider, providerId)처럼 비어있을 확률이 높은 Nullable 컬럼들은 처음부터 jdbcType을 명시하는 습관을 들이는 것이 런타임 에러를 방지하는 가장 확실한 방법이다.