Skip to content

[Bug] boolean = true 인스턴스 필드 초기화값이 DDL DEFAULT '0'으로 반영됨 #117

Description

@yyubin

boolean 필드 초기값 DDL DEFAULT 미반영 — 설계 검토 안건

현상

@Column(nullable = false)
private boolean isActive = true;
-- 기대
`active` TINYINT(1) NOT NULL DEFAULT '1'

-- 실제
`active` TINYINT(1) NOT NULL DEFAULT '0'

= false 또는 초기값 미지정 케이스는 DEFAULT '0'이 정확히 생성됨.
= true 케이스만 DEFAULT '0'으로 잘못 생성됨.


근본 원인

1. 타입 매퍼 하드코딩

MySqlJavaTypeMapper에서 boolean/Boolean 타입의 defaultValue"0"으로 정적 정의됨.

entry("java.lang.Boolean", new MysqlJavaType(..., "0")),
entry("boolean",           new MysqlJavaType(..., "0")),

2. Processor가 DEFAULT를 세팅하지 않음

ColumnBuilderFactoryColumnModel.defaultValue를 채우지 않으므로 항상 null.

MySqlDialect.getColumnDefinitionSql() 우선순위:

1순위: ColumnModel.defaultValue    → null (processor에서 미설정)
2순위: UUID 생성 전략
3순위: JavaTypeMapper.defaultValue → 항상 "0"  ← 여기서 결정됨

3. APT API 제약 — 핵심

VariableElement.getConstantValue()컴파일 타임 상수 변수(static final 필드)에서만 값을 반환함.

// static final → getConstantValue() = true (읽기 가능)
private static final boolean ENABLED = true;

// 일반 인스턴스 필드 → getConstantValue() = null (읽기 불가)
private boolean isActive = true;

JPA 엔티티에서 사용하는 인스턴스 필드의 초기화자는 바이트코드에만 존재하며, APT의 언어 모델(javax.lang.model) 레벨에서는 접근 수단이 없음.


영향 범위

  • JPA 경유 INSERT: Hibernate가 Java 필드값을 명시적으로 바인딩하므로 런타임 무영향
  • DB 직접 INSERT / seed 스크립트: DEFAULT 의존 시 false로 저장됨 → 비즈니스 로직 불일치
  • 스키마 가독성: 필드 초기값이 true임에도 스키마상 DEFAULT '0'으로 표현됨

해결 방향 검토

방향 A — @DefaultValue Jinx 전용 어노테이션 추가

@Column(nullable = false)
@DefaultValue("1")
private boolean isActive = true;

Processor에서 @DefaultValue를 읽어 ColumnModel.defaultValue에 세팅.

장점

  • APT 제약 우회 가능
  • 명시적이고 의도가 명확함
  • boolean 외 다른 타입(String, int 등)에도 동일하게 활용 가능

단점

  • Jinx 전용 어노테이션 → JPA 표준 밖의 침습적 변경
  • Java 필드 초기값과 @DefaultValue가 달라질 경우 혼란 가능
  • 기존 코드에 어노테이션 추가 작업 필요 (9개 필드 → 실제 운영 코드에선 더 많을 수 있음)

방향 B — 타입 매퍼의 boolean "0" 제거

MySqlJavaTypeMapper에서 boolean의 defaultValue = "0" 제거.
boolean 컬럼에 DEFAULT 절 자체를 생성하지 않음.

-- 변경 전
`active` TINYINT(1) NOT NULL DEFAULT '0'

-- 변경 후
`active` TINYINT(1) NOT NULL

장점

  • 코드 변경 최소
  • "잘못된 DEFAULT '0'" 문제 즉시 해소
  • JPA 표준 범위 내 동작 (JPA는 DDL DEFAULT를 정의하지 않음)

단점

  • = false로 초기화된 필드도 DEFAULT 절이 사라짐
  • DB 직접 INSERT 시 NOT NULL 컬럼에 값을 명시해야 함
    (단, 이는 현행 DEFAULT '0'보다 오히려 명확한 동작)
  • 기존 마이그레이션 파일과의 diff가 발생함 (운영 중인 경우)

방향 C — @Column(columnDefinition) workaround 문서화 (현행 유지)

변경 없이 명시적 DDL 지정 방법을 안내.

@Column(nullable = false, columnDefinition = "TINYINT(1) NOT NULL DEFAULT 1")
private boolean isActive = true;

장점

  • 코드 변경 없음
  • JPA 표준 방법

단점

  • 사용자가 직접 DDL 문자열을 작성해야 함 (타입명, 방언 의존)
  • 근본 원인(잘못된 DEFAULT '0') 미해결 → 버그가 아니라 설계로 인정하는 것

방향 D — A + B 병행 (중장기)

  1. 즉시: 타입 매퍼에서 boolean "0" 제거 (방향 B)
  2. 추후: @DefaultValue 어노테이션 추가 (방향 A)

B로 "잘못된 DEFAULT '0'" 문제를 선제 해소한 뒤,
필요한 경우 @DefaultValue로 명시적 제어를 제공.


정리

질문 내용
JPA DDL에서 boolean DEFAULT는 필수인가? JPA 스펙 미정의. 선택사항.
DEFAULT '0'이 없으면 문제가 되는가? NOT NULL 컬럼이면 INSERT 시 값 명시 필요. JPA 경유엔 무관.
= true 필드의 실제 사용 빈도는? 도메인 검증 결과 9개. 운영 코드에서는 더 많을 가능성.
어노테이션 추가(A) vs 타입 매퍼 정리(B) 우선? B가 더 빠르고 부작용 적음. A는 별도 기능 범위.

제안

단기: 방향 B 채택
타입 매퍼에서 boolean DEFAULT 하드코딩 제거.
"잘못된 DEFAULT '0'" 출력을 즉시 해소하고, 스키마를 JPA 표준에 가깝게 유지.

중장기: 방향 A를 별도 이슈로 트래킹
@DefaultValue 어노테이션은 boolean 한정이 아닌 범용 기능이므로,
별도 스펙 설계 후 독립 구현.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingfeaturewant a new featureroadmap

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions