빌드는 되는데 왜 안 뜰까? Kotlin 캐시, JPA 검증, 빈 주입 에러를 계층별로

궁금하면 500원·2026년 4월 9일

미생의 개발 이야기

목록 보기
76/81

캐시 충돌부터 스키마 검증, 빈 등록 실패 빌드가 자꾸 터질 때, 로그를 보면 원인이 보인다

프로젝트를 만지다 보면 “왜 또 안 되냐” 싶은 순간이 한 번이 아니라 줄줄이 온다.
특히 Kotlin + Spring Boot + JPA + PostgreSQL 조합에서는, 에러가 한 번 시작되면 전혀 다른 문제처럼 보여도 사실은 각 계층에서 다른 종류의 실패가 순서대로 드러나는 경우가 많다.

이번에 겪은 흐름도 딱 그랬다.

처음에는 Kotlin 컴파일 데몬이 죽었고, 그다음에는 Hibernate 스키마 검증에서 막혔고, 마지막에는 ObjectMapper 빈이 없어서 애플리케이션이 뜨지 않았다.
얼핏 보면 전부 별개 사고처럼 보이지만, 로그를 차근히 뜯어보면 원인과 레이어가 꽤 분명하게 갈린다.

1. 코드가 틀린 게 아니라 컴파일 캐시가 꼬였다

처음 마주친 건 Kotlin 컴파일 실패였다.
그런데 내용을 보면 문법 에러나 타입 에러가 아니라 이런 쪽이었다.

  • Daemon compilation failed
  • Could not close incremental caches
  • Storage ... is already registered

이런 메시지는 대체로 소스가 틀려서가 아니라 Kotlin daemon과 incremental cache가 충돌한 상황이다.
쉽게 말해, 이전 빌드 흔적을 정리하지 못했는데 새 빌드가 또 들어오면서 캐시 파일 등록이 꼬인 거다.

이때 흔한 실수는 경고를 범인으로 보는 거다.
로그 위쪽에 deprecated 경고가 잔뜩 찍혀 있으니 거기에 시선이 가는데, 실제로 애플리케이션을 멈춘 건 경고가 아니라 캐시 정리 실패였다.

이런 경우 우선순위는 명확하다.

  • Gradle daemon 중지
  • build 캐시 삭제
  • 필요하면 .gradle까지 정리
  • 다시 clean build

즉, 이 단계에서 중요한 건 “왜 문법이 틀렸지?”가 아니라
“지금 이건 코드 문제냐, 빌드 인프라 문제냐”를 먼저 구분하는 것이다.

2. 애플리케이션이 뜨지 않는 진짜 이유는 JPA 스키마 검증 실패였다

캐시 문제를 넘기고 나니 이번에는 애플리케이션 부팅이 JPA에서 멈췄다.
핵심 로그는 이거였다.

tb_banner.reflct_at 컬럼의 타입이 실제 DB에서는 bpchar, 즉 char(1)인데, Hibernate는 varchar(1)로 기대하고 있었다.

이건 자주 나오는 함정이다.
엔티티에서는 보통 이렇게 쓴다.

@Column(name = "reflct_at", length = 1)
var reflctAt: String? = null

개발자는 “1글자니까 됐지”라고 생각하지만, Hibernate는 이걸 varchar(1) 쪽으로 해석할 수 있다.
반면 PostgreSQL의 실제 컬럼은 char(1)이면 내부적으로 bpchar로 보인다.
결국 Hibernate의 validate 단계에서 “타입 다름” 판정이 나고, SessionFactory 생성 자체가 실패한다.

여기서 중요한 포인트는 이거다.

이건 단순 예외가 아니라 엔티티 정의와 실제 스키마 간 계약 위반이다.

즉, 해결도 둘 중 하나뿐이다.

  • DB를 varchar(1)로 바꾸거나
  • 엔티티를 char(1)로 명시해서 맞추거나

예를 들어 레거시 테이블에서 Y/N 플래그 컬럼을 오래 써왔다면, 오히려 엔티티 쪽을 아래처럼 맞추는 게 더 자연스럽다.

@Column(name = "reflct_at", columnDefinition = "char(1)")
var reflctAt: String? = null

여기서 많이 하는 나쁜 선택이 하나 있다.
ddl-auto=validate가 거슬린다고 검증을 꺼버리는 것이다.

그건 문제를 해결하는 게 아니라 경고등을 검은 테이프로 가리는 행동에 가깝다.
부팅은 될 수 있어도, 운영에서 더 골치 아픈 형태로 돌아온다.

3. 이번엔 DB가 아니라 스프링 컨테이너가 막혔다

스키마 문제를 넘기고 나면 또 다른 실패가 나왔다.

MbrdJdbcEditorDocumentRepository 생성자에서 ObjectMapper를 주입받는데, 해당 타입의 빈을 찾지 못한다는 에러였다.

이건 앞선 두 문제와 결이 다르다.

  • 첫 번째는 빌드 캐시 문제
  • 두 번째는 DB 스키마 문제
  • 세 번째는 스프링 빈 구성 문제

즉, 이제는 애플리케이션 레이어에서 DI 구성 자체가 맞지 않는 상태다.

특히 이 상황은 Spring Boot 4 환경이라면 더 민감하다.
코드에서는 com.fasterxml.jackson.databind.ObjectMapper를 요구하고 있는데, 현재 환경의 JSON 구성과 맞지 않거나, 관련 의존성/자동설정이 예상대로 올라오지 않으면 이런 식으로 빈이 비게 된다.

겉으로는 “ObjectMapper 하나 없네?”처럼 보여도, 실제로는 다음 둘 중 하나일 가능성이 크다.

  • 의존성 구성이 현재 프레임워크 버전과 안 맞음
  • 코드가 기대하는 Jackson 타입과 실제 자동설정 방향이 다름

이 단계에서 해야 할 질문은 단순하다.

  • 지금 ObjectMapper를 어떤 패키지로 import하고 있는가?
  • build 설정에서 Jackson 관련 의존성은 어떻게 잡혀 있는가?
  • 프레임워크 버전에 맞는 JSON 설정이 실제로 올라오고 있는가?

즉, 이 문제는 “빈 하나 수동 등록하면 끝”일 수도 있지만, 제대로 보면 프레임워크 버전 업 과정에서 생긴 호환성 문제일 가능성이 높다.

4. 로그를 볼 때 중요한 건 “메시지 양”이 아니라 “실패 계층”이다

실무에서 로그가 길어지면 사람은 쉽게 지친다.
위에서부터 읽다가 스택트레이스에 파묻히고, 중간쯤 가면 멘탈이 먼저 꺼진다.
하지만 대부분의 장애는 로그가 길어서 어려운 게 아니라, 어느 레이어의 실패인지 구분하지 못해서 어렵다.

이번 흐름만 봐도 딱 나뉜다.

빌드 레이어

Kotlin daemon / incremental cache 충돌

ORM 레이어

Hibernate schema validation 실패

컨테이너 레이어

Spring bean 생성 실패

이렇게 나누고 보면, 사실 한 번에 세 문제를 동시에 푸는 게 아니다.
순서대로 하나씩 제거하면 된다.

  1. 빌드가 안 되면 캐시/데몬부터 정리
  2. 부팅 중 JPA가 죽으면 엔티티-DB 타입 정합성 확인
  3. 그다음 빈 주입 실패가 나오면 의존성/자동설정 점검

이런 식으로 접근하면 로그가 길어도 안 쫄린다.
로그는 장문의 협박문이 아니라, 그냥 범인이 자기 위치를 순서대로 불고 있는 거다.

5. 이번 사례가 보여주는 핵심

이번 흐름에서 얻을 수 있는 교훈은 꽤 분명하다.

첫째, 에러는 한 번에 하나씩 푼다

앞의 에러를 해결해야 뒤의 에러가 보인다.
처음 문제를 덮어놓고 전체를 한 번에 해석하려고 하면 머리만 아프다.

둘째, 경고와 실패를 구분해야 한다

deprecated 경고가 많아도, 실제 부팅 실패 원인은 전혀 다른 데 있을 수 있다.

셋째, 엔티티와 DB는 “비슷하면 된다”가 아니다

char(1)varchar(1)도 Hibernate 입장에서는 다른 계약이다.
특히 validate 모드에서는 더 냉정하다.

넷째, 프레임워크 업그레이드는 생각보다 깊게 영향을 준다

Spring Boot 메이저 버전 변경은 단순 문법 수정이 아니라,
자동설정, 라이브러리 버전, 주입 가능한 타입까지 영향을 준다.

마무리

개발하다 보면 “왜 이래?”가 절로 나오는 순간이 있다.
그런데 로그를 제대로 읽으면, 대부분의 장애는 감정 문제가 아니라 구조 문제다.

  • 캐시가 꼬였는지
  • 스키마가 안 맞는지
  • 빈 구성이 비었는지

이걸 구분하는 순간, 긴 로그도 덜 무섭다.
결국 중요한 건 에러 메시지를 많이 읽는 게 아니라, 어느 계층에서 실패했는지 먼저 분류하는 습관이다.

한마디로 정리하면 이렇다.

빌드 실패는 코드 탓이 아닐 수 있고, 부팅 실패는 DB 탓일 수 있고, 마지막엔 설정 탓일 수 있다.
로그는 시끄럽지만, 원인은 생각보다 정직하다.

profile
그냥 코딩할래요 재미있어요

0개의 댓글