개발/DB

ORA-01460 오류 해결|Spring JDBC에서 BLOB 저장할 때 발생하는 원인

Mr.Lee 하루 2026. 9. 3. 16:11

Oracle DB에 데이터를 저장하는 과정에서 다음 오류가 발생할 때가 있습니다.

ORA-01460: 요구된 변환은 실행될 수 없습니다.

영문 메시지는 다음과 같습니다.

ORA-01460: unimplemented or unreasonable conversion requested

저의 경우에는 Java Spring 환경에서 JDBC를 이용해 Oracle BLOB 컬럼에 데이터를 INSERT하는 과정에서 이 오류가 발생했습니다.

처음에는 SQL 문법 문제나 JDBC 자체의 문제라고 생각하기 쉽지만, BLOB을 저장하는 상황이라면 우선 Oracle 컬럼 타입과 Java에서 전달하는 데이터 타입이 정확히 일치하는지 확인해야 합니다.

이번 글에서는 ORA-01460이 발생했을 때 확인해야 할 내용을 Spring JDBC와 Oracle BLOB 기준으로 정리해보겠습니다.


ORA-01460 오류란?

Oracle에서 ORA-01460은 기본적으로 지원되지 않거나 적절하지 않은 데이터 변환을 요청했을 때 발생하는 오류입니다.

쉽게 말하면 Oracle 입장에서는 다음과 같은 상황입니다.

Java에서 전달한 값
        ↓
Oracle JDBC Driver
        ↓
Oracle 컬럼 타입으로 변환 시도
        ↓
변환할 수 없는 조합
        ↓
ORA-01460

따라서 오류 메시지만 보고 SQL 문법만 계속 수정하기보다는 어떤 Java 타입을 어떤 Oracle 컬럼에 넣고 있는지 확인하는 것이 중요합니다.


제가 먼저 확인한 부분

예를 들어 다음과 같은 테이블이 있다고 가정하겠습니다.

CREATE TABLE FILE_TEST (
    FILE_ID      NUMBER,
    FILE_NAME    VARCHAR2(200),
    FILE_DATA    BLOB
);

FILE_DATA 컬럼은 BLOB입니다.

Java에서는 파일 내용을 다음처럼 byte[]로 가지고 있을 수 있습니다.

byte[] fileData = Files.readAllBytes(file.toPath());

이 데이터를 DB에 저장할 때 문제가 되는 대표적인 경우는 BLOB 데이터를 일반 문자열이나 범용 Object처럼 전달하는 경우입니다.


잘못된 접근 예시

프로젝트 구조에 따라 아래와 비슷하게 값을 전달하는 경우가 있습니다.

Object[] params = {
    fileId,
    fileName,
    fileData
};

jdbcTemplate.update(sql, params);

항상 이 코드가 오류를 발생시키는 것은 아닙니다.

하지만 실제 프로젝트에서는 다음과 같은 요소가 결합되면서 문제가 발생할 수 있습니다.

  • Oracle JDBC Driver 버전
  • Spring JDBC 버전
  • 실제 BLOB 데이터 크기
  • 파라미터 타입 추론
  • Map<String, Object> 사용
  • 공통 DAO 또는 공통 INSERT 로직
  • 문자열과 바이너리 데이터 혼합 처리

특히 자체적으로 만든 공통 DAO에서 모든 데이터를 String이나 일반 Object로 변환한 뒤 SQL에 전달한다면 BLOB 컬럼에서 문제가 발생할 가능성이 커집니다.


핵심은 BLOB을 문자열로 처리하지 않는 것

파일 데이터는 기본적으로 바이너리입니다.

예를 들어 다음과 같은 변환은 피하는 것이 좋습니다.

String blobData = new String(fileData);

그리고 그 문자열을 다시 BLOB 컬럼에 넣는 식으로 처리하면 안 됩니다.

params.addValue("FILE_DATA", blobData);

BLOB이라면 가능하면 처음부터 끝까지

파일
 ↓
byte[]
 ↓
PreparedStatement
 ↓
BLOB

또는

파일
 ↓
InputStream
 ↓
PreparedStatement
 ↓
BLOB

구조를 유지하는 것이 좋습니다.


방법 1. PreparedStatement에서 setBytes 사용

데이터 크기가 크지 않다면 가장 직관적인 방법입니다.

String sql = """
    INSERT INTO FILE_TEST (
        FILE_ID,
        FILE_NAME,
        FILE_DATA
    ) VALUES (?, ?, ?)
    """;

jdbcTemplate.update(
    sql,
    fileId,
    fileName,
    fileData
);

좀 더 타입을 명확하게 처리하고 싶다면 PreparedStatement를 직접 사용할 수 있습니다.

jdbcTemplate.update(
    connection -> {
        PreparedStatement ps = connection.prepareStatement(sql);

        ps.setLong(1, fileId);
        ps.setString(2, fileName);
        ps.setBytes(3, fileData);

        return ps;
    }
);

핵심 부분은 이것입니다.

ps.setBytes(3, fileData);

BLOB 컬럼에 들어갈 데이터를 문자열이 아니라 바이너리 데이터로 전달합니다.


방법 2. setBinaryStream 사용

파일 크기가 크다면 byte[] 전체를 메모리에 올리는 방식보다는 InputStream 방식이 더 적절할 수 있습니다.

예를 들면 다음과 같습니다.

String sql = """
    INSERT INTO FILE_TEST (
        FILE_ID,
        FILE_NAME,
        FILE_DATA
    ) VALUES (?, ?, ?)
    """;

jdbcTemplate.update(
    connection -> {
        PreparedStatement ps = connection.prepareStatement(sql);

        ps.setLong(1, fileId);
        ps.setString(2, fileName);

        InputStream inputStream = new FileInputStream(file);

        ps.setBinaryStream(
            3,
            inputStream,
            file.length()
        );

        return ps;
    }
);

Spring JDBC에서도 BLOB은 byte[] 또는 InputStream을 이용한 방식으로 처리할 수 있습니다.

대용량 파일을 다룬다면 InputStream 방식도 검토할 만합니다.


NamedParameterJdbcTemplate 사용 시 주의

실무에서는 NamedParameterJdbcTemplate을 많이 사용합니다.

예를 들어 다음과 같습니다.

String sql = """
    INSERT INTO FILE_TEST (
        FILE_ID,
        FILE_NAME,
        FILE_DATA
    )
    VALUES (
        :fileId,
        :fileName,
        :fileData
    )
    """;

MapSqlParameterSource params = new MapSqlParameterSource();

params.addValue("fileId", fileId);
params.addValue("fileName", fileName);
params.addValue("fileData", fileData);

namedParameterJdbcTemplate.update(sql, params);

이것도 정상 동작할 수 있습니다.

하지만 ORA-01460 같은 타입 관련 오류를 추적하고 있다면 SQL 타입을 명시해보는 것도 방법입니다.

params.addValue(
    "fileData",
    fileData,
    Types.BLOB
);

즉 다음처럼 처리합니다.

MapSqlParameterSource params = new MapSqlParameterSource();

params.addValue("fileId", fileId, Types.NUMERIC);
params.addValue("fileName", fileName, Types.VARCHAR);
params.addValue("fileData", fileData, Types.BLOB);

공통 DAO나 자동 파라미터 바인딩을 사용하는 프로젝트라면 이렇게 타입을 명시하는 것만으로 문제 범위를 좁히는 데 도움이 됩니다.


Oracle 컬럼 타입부터 반드시 확인

Java 코드만 확인해서는 안 됩니다.

Oracle에서 실제 컬럼 타입을 확인합니다.

DESC FILE_TEST;

또는 다음 SQL로 확인할 수 있습니다.

SELECT
    COLUMN_NAME,
    DATA_TYPE,
    DATA_LENGTH
FROM USER_TAB_COLUMNS
WHERE TABLE_NAME = 'FILE_TEST';

결과가 다음과 같은지 확인합니다.

FILE_ID      NUMBER
FILE_NAME    VARCHAR2
FILE_DATA    BLOB

의외로 운영 DB와 개발 DB의 컬럼 타입이 다른 경우도 있습니다.

예를 들어 개발 DB는

FILE_DATA BLOB

인데 운영 DB가 오래된 스키마라

FILE_DATA VARCHAR2

등으로 구성되어 있다면 동일한 Java 코드라도 한쪽에서만 오류가 발생할 수 있습니다.

데이터 이관이나 오래된 시스템에서는 반드시 실제 DB 스키마를 확인하는 것이 좋습니다.


공통 DAO를 사용한다면 더 주의해야 합니다

제가 ORA-01460 같은 오류에서 특히 의심하는 부분이 공통 INSERT 로직입니다.

예를 들어 다음과 같은 구조입니다.

Map<String, Object> data = new HashMap<>();

data.put("FILE_ID", fileId);
data.put("FILE_NAME", fileName);
data.put("FILE_DATA", fileData);

commonDao.insert("FILE_TEST", data);

겉으로 보기에는 문제가 없어 보입니다.

하지만 내부에서 다음과 같이 처리하고 있다면 문제가 생길 수 있습니다.

ps.setString(index, String.valueOf(value));

모든 컬럼을 일괄적으로 setString()으로 처리하는 구조입니다.

일반 VARCHAR2 컬럼에서는 문제가 없지만 BLOB에는 적절하지 않습니다.

따라서 공통 DAO에서는 데이터 타입에 따라 분기해야 합니다.

예를 들면 다음과 같습니다.

if (value instanceof byte[]) {

    ps.setBytes(index, (byte[]) value);

} else if (value instanceof String) {

    ps.setString(index, (String) value);

} else if (value instanceof Integer) {

    ps.setInt(index, (Integer) value);

} else {

    ps.setObject(index, value);
}

조금 더 안전하게 설계하려면 Java 타입만 보는 것이 아니라 Oracle 메타데이터까지 확인해서 JDBC 타입을 결정하는 방법도 있습니다.


데이터 이관 프로그램에서는 특히 발생하기 쉽습니다

A DB에서 B DB로 데이터를 옮기는 프로그램을 만든다고 가정해보겠습니다.

보통 다음 구조를 사용합니다.

Source DB
   ↓
ResultSet
   ↓
Map<String, Object>
   ↓
Target DB

일반적인 NUMBER, VARCHAR2, DATE 컬럼은 비교적 쉽게 처리할 수 있습니다.

하지만 BLOB이나 CLOB이 포함되면 이야기가 달라집니다.

특히 다음과 같은 처리는 위험합니다.

Object value = rs.getObject(columnName);

targetParams.put(columnName, value);

그리고 Target DB에 무조건

ps.setObject(index, value);

로 넣어버리는 구조입니다.

Oracle LOB을 포함하는 데이터 이관 프로그램이라면 컬럼 타입을 별도로 분기하는 것이 좋습니다.


ResultSet에서 BLOB 읽기

BLOB 컬럼이라면 다음과 같이 가져올 수 있습니다.

Blob blob = rs.getBlob("FILE_DATA");

그리고 byte 배열이 필요하다면

byte[] data = blob.getBytes(
    1,
    (int) blob.length()
);

로 가져올 수 있습니다.

또는 스트림으로 처리할 수도 있습니다.

InputStream inputStream =
    rs.getBinaryStream("FILE_DATA");

대용량 데이터를 다루는 이관 프로그램이라면 스트림 방식이 메모리 사용 측면에서 더 유리할 수 있습니다.


BLOB과 CLOB을 혼동하지 않았는지도 확인

Oracle에서 둘은 용도가 다릅니다.

Oracle 타입 데이터
BLOB Binary
CLOB Character
VARCHAR2 문자열
RAW Binary
NUMBER 숫자

예를 들어 JSON이나 XML처럼 문자열 기반 데이터라면 CLOB이 더 적절할 수도 있습니다.

반면 이미지, PDF, ZIP 파일 등이라면 BLOB을 사용합니다.

PDF → BLOB
이미지 → BLOB
ZIP → BLOB

긴 TEXT → CLOB
XML 문자열 → CLOB
JSON 문자열 → CLOB

따라서 저장하려는 데이터 자체의 성격도 확인해야 합니다.


ORA-01460 발생 시 체크리스트

저라면 다음 순서로 확인합니다.

1. 오류가 발생하는 컬럼 확인
        ↓
2. Oracle 실제 컬럼 타입 확인
        ↓
3. Java에서 전달하는 타입 확인
        ↓
4. setString / setObject 사용 여부 확인
        ↓
5. BLOB이면 setBytes 또는 setBinaryStream 검토
        ↓
6. NamedParameter 사용 시 Types.BLOB 명시
        ↓
7. 공통 DAO 내부의 타입 변환 확인
        ↓
8. Oracle JDBC Driver 버전 확인
        ↓
9. 개발 DB와 운영 DB 스키마 비교

특히 BLOB 저장 중 발생했다면 아래 세 가지부터 보는 것이 빠릅니다.

Oracle 컬럼 = BLOB 인가?

Java 데이터 = byte[] 또는 InputStream 인가?

중간에서 String으로 변환되고 있지 않은가?

로그를 찍어서 실제 타입을 확인하는 것도 중요

문제가 계속 발생한다면 DB에 전달하기 직전 값을 확인합니다.

예를 들어 다음과 같습니다.

Object value = params.get("FILE_DATA");

System.out.println(
    value == null
        ? "null"
        : value.getClass().getName()
);

정상적으로 byte[]를 예상했다면 다음과 비슷하게 나타납니다.

[B

Java에서 [Bbyte[]를 의미합니다.

그런데 다음처럼 나오면 예상과 다른 타입이 전달되고 있는 것입니다.

java.lang.String

공통 프레임워크를 사용하다 보면 소스에서는 byte[]로 만들었는데 중간 처리 과정에서 String으로 변환되는 경우가 있기 때문에 DB 직전의 실제 타입을 확인하는 것이 중요합니다.


정리

ORA-01460: 요구된 변환은 실행될 수 없습니다 오류는 단순한 SQL 문법 오류라기보다는 Oracle이 처리할 수 없는 데이터 타입 변환이 발생하고 있다는 신호로 보는 것이 좋습니다.

특히 Spring JDBC에서 BLOB 데이터를 INSERT하는 과정이라면 다음을 먼저 확인해보세요.

BLOB 컬럼
   ↕
byte[] 또는 InputStream

이 관계가 중간에 깨지지 않아야 합니다.

제가 우선 확인할 부분은 다음 세 가지입니다.

  1. Oracle 실제 컬럼이 정말 BLOB인지 확인
  2. Java에서 byte[] 또는 InputStream으로 전달되는지 확인
  3. 공통 DAO에서 String으로 강제 변환하고 있지 않은지 확인

그리고 JdbcTemplate, NamedParameterJdbcTemplate, 자체 공통 DAO를 사용하고 있다면 파라미터가 최종적으로 PreparedStatement에 어떤 JDBC 타입으로 바인딩되는지까지 확인하면 원인을 찾는 데 도움이 됩니다.

BLOB 때문에 ORA-01460이 발생한다면 SQL문 자체만 계속 변경하지 말고 Java 타입 → JDBC 바인딩 → Oracle 컬럼 타입 순서로 추적해보는 것을 추천합니다.


자주 묻는 질문

ORA-01460은 BLOB에서만 발생하나요?

아닙니다. Oracle이 지원하지 않는 형식 변환을 요청하는 여러 상황에서 발생할 수 있습니다. 다만 Java/Spring에서 BLOB이나 CLOB 같은 LOB 데이터를 처리할 때 타입 바인딩 문제로 만나게 되는 경우가 있습니다.

JdbcTemplate에서 byte[]를 바로 넣어도 되나요?

가능합니다. 다만 프로젝트 환경과 드라이버, 공통 DAO 구조 등에 따라 타입 추론이 원하는 방식으로 이루어지지 않을 수 있습니다. 문제가 발생한다면 PreparedStatement#setBytes() 또는 명시적인 JDBC 타입 지정을 검토하는 것이 좋습니다.

BLOB인데 setString을 사용하면 안 되나요?

바이너리 파일 데이터를 저장하는 목적이라면 문자열로 강제 변환해서 처리하는 방식은 피하는 것이 좋습니다. byte[], InputStream, setBytes(), setBinaryStream()처럼 바이너리 데이터를 보존하는 방법을 사용하는 것이 적절합니다.

setObject를 사용하면 안 되나요?

항상 문제가 되는 것은 아닙니다. 하지만 ORA-01460 같은 타입 관련 오류를 추적하고 있다면 setObject()에 의존하기보다 실제 JDBC 타입을 명확하게 지정해서 테스트하는 것이 원인을 찾기 쉽습니다.

BLOB 파일이 클 때는 어떻게 처리하나요?

큰 파일을 전부 byte[]로 메모리에 올리면 메모리 사용량이 커질 수 있습니다. 이런 경우 InputStreamsetBinaryStream() 방식의 스트리밍 처리를 검토할 수 있습니다.