1. Hibernate의 best guess
에러 구문
GenerationTarget encountered exception accepting command : Error executing DDL "alter table if exists images alter column data set data type oid" via JDBC [오류: "data" 칼럼의 자료형을 oid 형으로 형변환할 수 없음. Hint: "USING data::oid" 구문을 추가해야 할 것 같습니다.]
org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL "alter table if exists images alter column data set data type oid" via JDBC [오류: "data" 칼럼의 자료형을 oid 형으로 형변환할 수 없음\n Hint: "USING data::oid" 구문을 추가해야 할 것 같습니다.]
ddl-auto: update 의 동작 방식
# src/main/resources/application.yml
jpa:
database-platform: org.hibernate.dialect.PostgreSQLDialect
hibernate:
ddl-auto: update
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
show-sql: false
`spring.jpa.hibernate.ddl-auto=update`를 설정하면 Hibernate는 시작 설정에서 아래처럼 동작한다 :
- Entity 어노테이션을 읽는다. ("metadata")
- 현재 DB 스키마를 읽는다. (tables / columns / types)
- 이 둘을 비교한다.
- 만약 다른 점이 발견된다면 ALTER DDL을 생략한다.
- ALTER TABLE ... ADD COLUMN ...
- ALTER TABLE ... ALTER COLUMN ... TYPE ...
따라서, DB 스키마는 Hibernate의 최적 추측에 따라 자동 변환된다.
2. 왜 @Lob 어노테이션이 문제인가
초기 ImageEntity
data class ImageEntity(
@Id
var id: String = "",
@Column(name = "content_type", nullable = false)
var contentType: String = "",
@Lob
@Column(nullable = false)
var data: ByteArray = ByteArray(0)
@Lob 개념
- Large Object의 준말로, 단일 물리 DB type을 지정하지 않는다.
"이 필드는 매우 클 수 있으므로, 일반적인 column type 보단 LOB type으로 취급해라"
JPA 측면에서 :
- @Lob + String → CLOB (Character Large Object)
- @Lob + byte[] (ByteArray) → BLOB (Binary Large Object)
따라서 초기 ImageEntity의 ByteArray("data")를 설정하면, 이는 BLOB으로 지정된다.
문제 지점
JPA는 "LOB"이라고 할 뿐, 각 DB가 LOB를 다루는 방식을 표준화하지 않는다.
매핑은 언어에 의존(dialect-dependent)한다 :
- MySQL은 BLOB을 잘 매핑할 수도 있다.
- PostgreSQL은 "large binary"를 표현하는 방법이 한 가지 이상이다.
Hibernate의 추측이 위험한 이유가 바로 이것이다.
3. PostgreSQL은 두 가지 "binary storage 모델"을 가진다.
bytea (inline binary)
- Binary data를 위한 정상적인 column type
- Bytes는 테이블 내에 행(row)의 일부로 저장된다.
- 많은 app에서 이미지나 파일을 저장하기 위한 보편적 선택
oid (Large Object reference)
- 자체적으로 bytes는 아니다.
- OID pointer(ID)를 저장한다.
- PostgreSQL으로 관리되는 독립된 Large Object storage area를 가리킨다.
- Large Objects API를 통해 실제 bytes를 저장하고 추출한다.
따라서, bytea는 "bytes가 여기 있다"는 것이고, oid는 "ID가 여기 있고; 실제 bytes는 다른 곳에 있다"는 것이다.
서로 호환 가능한 것이 아니다.
PostgreSQL에서, @Lob은 Hibernate로 하여금 OID/large object semantics를 선택하게 한다.
4. 문제 동작 방식
Hibernate는 ByteArray + @Lob 조합을 "LOB" → "Large Object" → OID 형식으로 해석하려고 했다.
Spring startup 과정(ddl-auto: update)은 :
- Image Entity는 "OID"라고 말하는데
- DB column은 "bytea"로 취급한 것이다.
그래서 Hibternate는 ALTER 명령어를 통해 스키마 정렬을 시도했다.
ALTER TABLE images
ALTER COLUMN data SET DATA TYPE oid;
PostgreSQL은 이를 암시적으로 bytea 값을 oid로 변환하는 것은 불가능하다고 표시한 것이다. 그래서 startup 과정에서 DDL 오류가 발생한 것이다.
("오류: "data" 칼럼의 자료형을 oid 형으로 형변환할 수 없음. Hint: "USING data::oid" 구문을 추가해야 할 것 같습니다.")
5. 실제 해결 방법
개정 ImageEntity
@Entity
@Table(name = "images")
data class ImageEntity(
@Id
var id: String = "",
@Column(name = "content_type", nullable = false)
var contentType: String = "",
@JdbcTypeCode(SqlTypes.VARBINARY)
@Column(nullable = false, columnDefinition = "bytea")
var data: ByteArray = ByteArray(0)
)
@JdbcTypeCode(SqlTypes.VARBINARY)
- JDBC 수준에서 Hibernate에게 "의도"를 알린다.
- "이를 LOB (OID) 경로가 아닌, 일반 binary column type으로 취급해라."
- 이를 통해 Hibernate는 "LOB 추측"을 그만둔다.
@Column(columnDefinition = "bytea"
- DB에게 정확한 column type을 알린다.
- "PostgreSQL에서 해당 column은 bytea다."
- 그래서 스키마 비교를 진행하는 동안 Hibernate는 :
- Entity를 bytea / varbinary intent로 보고, DB 또한 bytea로 본다.
결과적으로 type이 일치하여, 추가적인 `ALTER COLUMN ... TYPE oid`가 실행되지 않은 채 startup이 성공한다.
6. 결론
이번 문제는 PostgreSQL이 bytea를 oid로 자동 변환(auto-cast)할 수 없기 때문에 발생한 문제였다.
- bytea는 실제 bytes이고,
- oid는 bytes가 아닌, 참조 ID이다.
이를 전환(convert)하고자 한다면, 직접 로직을 명시해야 한다.
- Large Object entry를 만든다.
- bytes를 거기 저장한다.
- 자체 OID를 반환한다.
이는 "변환(cast)"가 아니라, 이전 절차(migration procedure)다.
문제 해결 GitHub 주소 : Link
'Backend' 카테고리의 다른 글
| [Spring] Spring 이해하기(2) - POJO 프로그래밍을 돕는 IoC/DI, AOP, PSA (1) | 2026.02.17 |
|---|---|
| [Spring] Spring 이해하기(1) - POJO, Java Bean, Spring Bean (0) | 2026.02.17 |
| [Backend] Kotlin JDBC 연결 유형 정리 (0) | 2026.02.02 |
| [Backend] 9주차 내용 정리 (0) | 2026.01.22 |
| [Backend] Kotlin 기반 HTTP 서버 구현 (0) | 2026.01.16 |