본문으로 건너뛰기
김신건의 로그

[Spring Boot] Hibernate Naming Strategy

· 수정 · 📖 약 2분 · 856자/단어 #spring #spring-boot #hibernate #jpa #naming-strategy #database
Hibernate Naming Strategy, PhysicalNamingStrategy, ImplicitNamingStrategy, SpringPhysicalNamingStrategy, snake_case naming, camelCase to snake_case, JPA 컬럼명 매핑, 하이버네이트 네이밍 전략

정의

Hibernate 가 entity 클래스 / 필드명 → DB table / column 명으로 변환하는 규칙. 두 단계로 분리:

단계역할인터페이스
Implicitentity / field 가 @Table / @Column 명시 없을 때 논리적 이름 생성 (예: UserUser)ImplicitNamingStrategy
Physical논리적 이름을 실제 DB 식별자 로 변환 (예: Useruser, createdAtcreated_at)PhysicalNamingStrategy

Spring Boot 의 기본:

  • Implicit: SpringImplicitNamingStrategy (entity = 클래스명, column = 필드명)
  • Physical: CamelCaseToUnderscoresNamingStrategy (camelCase → snake_case, 모두 소문자)

따라서 User.createdAt → 테이블 user, 컬럼 created_at. 명시적 어노테이션 없어도 모던 SQL 컨벤션 자동 적용.

자동 변환 예

@Entity
class UserAccount {
    @Id Long id;
    String firstName;
    String lastName;
    LocalDateTime createdAt;
    String OAuthToken;
}

기본 매핑 결과:

CREATE TABLE user_account (
    id           BIGINT PRIMARY KEY,
    first_name   VARCHAR(255),
    last_name    VARCHAR(255),
    created_at   TIMESTAMP,
    o_auth_token VARCHAR(255)    -- 주의: 연속 대문자 분해됨
);

연속 대문자 (OAuthToken) 가 o_auth_token 으로 변환되는 점 주의. 의도와 다르면 @Column(name = "oauth_token") 명시.

설정 파일에서 변경

spring:
  jpa:
    hibernate:
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
        implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
옵션동작
SpringPhysicalNamingStrategy 또는 CamelCaseToUnderscoresNamingStrategy (기본)camelCase → snake_case
PhysicalNamingStrategyStandardImpl변환 안 함 (논리명 그대로)
커스텀 클래스직접 구현

Hibernate 6+ 부터 SpringPhysicalNamingStrategyCamelCaseToUnderscoresNamingStrategy 의 alias 처럼 동작.

legacy DB 호환 (변환 안 함)

기존 DB 가 CreatedAt, UserAccount 처럼 PascalCase 면:

spring:
  jpa:
    hibernate:
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

이제 entity 의 식별자가 DB 에 그대로 매핑. @Column 어노테이션 거의 불필요해진다.

커스텀 Physical Strategy

특정 패턴 (예: prefix tbl_, 모든 컬럼 lowercase) 적용:

public class PrefixedPhysicalNamingStrategy implements PhysicalNamingStrategy {
    @Override
    public Identifier toPhysicalCatalogName(Identifier name, JdbcEnvironment ctx) { return name; }
    @Override
    public Identifier toPhysicalSchemaName(Identifier name, JdbcEnvironment ctx) { return name; }
    @Override
    public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment ctx) {
        if (name == null) return null;
        return Identifier.toIdentifier("tbl_" + camelToSnake(name.getText()));
    }
    @Override
    public Identifier toPhysicalSequenceName(Identifier name, JdbcEnvironment ctx) {
        return Identifier.toIdentifier("seq_" + camelToSnake(name.getText()));
    }
    @Override
    public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment ctx) {
        return Identifier.toIdentifier(camelToSnake(name.getText()).toLowerCase());
    }

    private String camelToSnake(String s) {
        return s.replaceAll("([a-z])([A-Z])", "$1_$2").toLowerCase();
    }
}
spring:
  jpa:
    hibernate:
      naming:
        physical-strategy: com.example.PrefixedPhysicalNamingStrategy

결과:

  • Usertbl_user
  • OrderItemtbl_order_item
  • createdAtcreated_at

다른 strategy 비교

SpringPhysicalNamingStrategy (Spring Boot 기본)

UserAccount.firstName → user_account.first_name

가장 흔한 modern SQL convention.

PhysicalNamingStrategyStandardImpl (Hibernate 기본)

UserAccount.firstName → UserAccount.firstName

논리명 그대로. 식별자 그대로 유지하려는 legacy DB 용.

LegacyJpaImpl

@Table(name="UserAccount")  // 명시 안 하면 클래스명 그대로
class UserAccount { ... }

JPA 2.0 이전 스타일.

Implicit Naming 의 역할

@JoinColumn 명시 안 했을 때 외래키 컬럼명 생성:

@Entity
class Post {
    @ManyToOne
    User author;   // @JoinColumn 없음
}

SpringImplicitNamingStrategy 가 적용 → author_id 자동 생성. 일반적으로 만족스러움.

@OneToMany(mappedBy="...") 의 join 테이블 명, @ElementCollection 컬렉션 테이블 명 등도 implicit 이 결정.

@Column / @Table 명시 vs 자동

// 자동 변환 의존
@Column
String firstName;   // → first_name

// 명시
@Column(name = "first_name")
String firstName;   // 동일하지만 명시적

규칙:

  • 새 프로젝트: 자동 변환 의존, @Column 생략
  • legacy DB / 컬럼명 특수 패턴: @Column(name=...) 명시
  • migration 도구 (Flyway, Liquibase) 와 entity 의 컬럼명 일치 강제: 명시 권장

실행 시점에 검증

spring:
  jpa:
    hibernate:
      ddl-auto: validate   # entity 매핑 vs DB 스키마 비교, 불일치 시 시작 실패

운영 환경 권장. naming strategy 가 의도와 다르면 시작 시점에 즉시 발견.

DDL 생성 모드별 영향

spring:
  jpa:
    hibernate:
      ddl-auto: create-drop   # 시작 시 drop + create, naming strategy 가 컬럼명 결정
    properties:
      hibernate:
        physical_naming_strategy: ...

ddl-auto:

  • create, create-drop: strategy 가 컬럼명 결정 → DB 에 반영
  • update: 기존 컬럼명 + strategy 둘 다 영향, 충돌 위험
  • validate: strategy 가 만드는 이름이 DB 에 있는지 비교
  • none (운영 권장): strategy 가 SELECT/INSERT 만들 때 컬럼명 사용

함정과 베스트 프랙티스

  • ddl-auto=update 와 strategy 변경 동시 금지: 기존 컬럼명과 새 컬럼명 둘 다 시도하면 schema 가 깨짐
  • 운영: ddl-auto=validate or none + Flyway/Liquibase: 자동 DDL 위험, migration 도구로 관리
  • @Column(name=...) 와 strategy 의 우선순위: 어노테이션이 항상 이김
  • 연속 대문자 함정: OAuthTokeno_auth_token. 의도 안 맞으면 명시
  • MySQL 의 case sensitivity 차이: Linux 는 case sensitive, Windows / Mac 은 insensitive. 항상 lowercase 사용 (기본 strategy 가 그렇게 함)
  • Postgres reserved word: user, order, group 등은 quote 필요. table name 으로 안 쓰는 게 안전
  • strategy 변경은 신중: 기존 entity 들의 매핑 일괄 변경됨

자주 보는 시나리오

시나리오 1: 새 Spring Boot 프로젝트

spring:
  jpa:
    hibernate:
      ddl-auto: create-drop   # 개발만

기본 SpringPhysicalNamingStrategy 가 알아서 snake_case. 손 댈 필요 없음.

시나리오 2: legacy Oracle DB (UPPER_CASE)

spring:
  jpa:
    hibernate:
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
@Entity
@Table(name = "USER_ACCOUNT")
class UserAccount {
    @Column(name = "FIRST_NAME") String firstName;
}

명시적 @Table / @Column 사용.

시나리오 3: Multi-tenant prefix

public class TenantPrefixedNamingStrategy implements PhysicalNamingStrategy {
    @Override
    public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment ctx) {
        String tenant = TenantContext.current();
        return Identifier.toIdentifier(tenant + "_" + name.getText());
    }
    // ...
}

tenant 별 별도 테이블 (위험한 패턴, 보통 row-level tenancy 가 낫지만 가능).

관련 위키

이 글의 용어 (5개)
[JPA] @Embeddable, @Embedded, @Converterspring
정의 @Embeddable 은 별도 테이블 없이 entity 의 일부로 포함되는 값 객체 (Value Object). 같은 의미적 그룹의 필드들을 하나의 클래스로 추출. Attr…
[JPA] PersistenceContext: 1차 캐시, dirty checking, flushspring
정의 PersistenceContext 는 가 관리하는 entity 의 메모리 저장소 + 변경 추적기. 트랜잭션 안에서만 살아 있다 ( 진입 시 생성, commit/rollbac…
[Spring Boot] 자동 구성과 @SpringBootApplicationspring
정의 Spring Boot의 핵심 가치 = 자동 구성(autoconfiguration). classpath의 라이브러리를 보고 합리적 기본 설정으로 Bean 등록. Tomcat이…
[Spring] @Conditional, Profile, @ConfigurationPropertiesspring
정의 Spring Boot는 조건부 Bean 등록 메커니즘을 제공한다. classpath, property, 다른 Bean 유무 등에 따라 자동 구성을 활성화/비활성화. , 어노…
[Spring] Spring Data JPA: Repository, Query Methodsspring
정의 Spring Data JPA는 JPA(Hibernate) 위에 Repository 추상화를 얹어 boilerplate를 제거. 인터페이스만 정의하면 Spring이 구현체 자…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기