org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: missing table [xxx]
그동안 JPA 구현체인 Hibernate를 자주 사용하며 ddl-auto
옵션을 지정하여 편리하게 DB Table을 생성(H2 In-memory DB를 사용하여 테스트하는 경우) 또는 검증했습니다. validate
옵션을 사용해 검증을 하는 과정에서 위와 같은 오류를 만나게 되었습니다. 분명 schema에 해당 테이블이 존재했지만 Hibernate는 테이블이 누락되었다고 인식하였습니다. 간단하게 해결 가능한 문제이지만 혹시나 비슷한 오류를 마주치거나 나중에도 다시 볼 수 있게 글로 정리해 보았습니다.
우선 Hibernate에 대해 간단하게 알아보도록 하겠습니다.
Hibernate는 객체 관계형 매핑(ORM: Object-Relational Mapping) 도구로, JPA(Java Persistence API) 사양에 대한 구현체입니다. 그래서 JPA를 지원하는 모든 환경에서 쉽게 사용할 수 있습니다. 쉽게 말해 자바 클래스와 데이터베이스 테이블의 매핑을 도와줍니다.
ddl-auto
처음에 언급한 ddl-auto
설정에 대해 알아보겠습니다.
JPA에는 애플리케이션을 시작할 때 데이터베이스에 대해 DDL 생성을 수행하도록 하는 설정이 있습니다. 이 설정은 두 가지 외부 속성을 통해 설정할 수 있습니다.
spring.jpa.generate-ddl
: boolean 형태로 설정하기 때문에 기능을 키거나 끄는 설정만 가능합니다.spring.jpa.hibernate.ddl-auto
: enum 형태로 설정하며 보다 세부적인 기능을 설정할 수 있는 Hibernate 기능입니다.위에서 말하는 enum은 SchemaAutoTooling입니다.
ddl-auto
옵션의 표준 Hibernate 속성 값은 none
, validate
, update
, create
, create-drop
으로 총 5가지입니다.
none
: 이름에서 알 수 있듯이 DDL 생성 설정을 끕니다.validate
: 테이블과 컬럼이 존재하는지 검사하고 만약 존재하지 않으면 예외를 던집니다. (DDL을 생성하지는 않으며 제가 설정한 값입니다!update
: Entity에 대해 테이블과 컬럼을 비교하고 차이점이 있으면 업데이트 합니다. (사용되지 않는 데이터베이스 테이블 또는 컬럼에 대해서는 삭제하지 않습니다)create
: 기존 테이블을 모두 드롭하고 새로 테이블을 생성합니다.create-drop
: 모든 작업이 완료된 후(ex. 애플리케이션 종료) 모든 테이블을 드롭합니다. 이후에 다시 애플리케이션을 실행할 때 테이블을 생성합니다.create와 create-drop은 비슷하지만 언제 테이블을 드롭하느냐의 차이가 있습니다.
이제 다시 오류로 돌아가 보겠습니다. 저는 ddl-auto validate 설정을 했고, 그러면 애플리케이션이 실행될 때 Entity에 맞게 테이블과 컬럼이 생성되어 있는지 검사를 할 것입니다. 하지만 테이블이 존재하지 않는다는 오류가 발생합니다.
'role'이라는 테이블이 존재하지 않는다고 합니다.
하지만 DB에는 Role
테이블을 생성해 두었고 @CollectionTable
의 name
속성을 "Role"
로 설정해 주었습니다.
에러 메시지에는 Role
이 아닌 role
이라고 출력되는 것이 이상했습니다.
테이블명을 role
로 수정하고 다시 실행해 보았습니다.
이제는 role 테이블은 문제가 없지만 user 테이블에서 문제가 발생합니다. User
도메인 클래스에도 @Table
의 name
속성을 통해 "User"
라고 명시적으로 설정했지만 계속 모두 소문자로 변환되어 검사가 진행되었습니다.
Hibernate에는 테이블 및 컬럼 명명 전략에 두 가지 종류가 있습니다. 논리적 명명 전략과 물리적 명명 전략이 있습니다. 이 둘의 차이점이 궁금하여 찾아보았습니다.
논리적 이름은 프로세스 사용자 인터페이스와 사용자 정의 응용 프로그램에 표시되는 이름이라고 합니다. 즉 스프링 애플리케이션 서버의 경우 Java와 Hibernate에서 사용하고 표시하는 이름이 논리적 이름입니다.
물리적 이름은 DB의 테이블을 식별할 때 사용하는 실제 이름이라고 합니다.
PhysicalNamingStrategy
우리가 필요한 부분은 물리적 이름의 명명 전략을 살펴봐야 합니다. Hibernate의 물리적 명명 전략은 PhysicalNamingStrategy
인터페이스가 그 역할을 담당합니다. 내장되어있는 구현체 클래스는 두 개가 있습니다. (사실 세 개지만 하나는 Deprecated 되었습니다.)
CamelCaseToUnderscoresNamingStrategy
클래스디버깅을 해본 결과 CamelCaseToUnderscoresNamingStrategy
가 기본 구현체로 사용되었습니다.
이 구현체의 내부 코드를 살펴보면 카멜케이스에서 스네이크케이스로 변경하고 마지막 반환하기 전에 모든 문자를 소문자로 변환합니다.
예시
NamingStrategy
->naming_strategy
PhysicalNamingStrategyStandardImpl
클래스다른 나머지 하나의 구현체 PhysicalNamingStrategyStandardImpl
은 아주 단순하게 @Table
과 @Column
의 name
속성(만약 지정하지 않았으면 테이블은 클래스 이름, 컬럼은 속성의 이름)에 설정한 문자열을 변환없이 그대로 반환합니다.
예시
NamingStrategy
->NamingStrategy
그래서 저는 PhysicalNamingStrategyStandardImpl
을 구현체로 사용하기로 정했습니다.
PhysicalNamingStrategy
구현체 설정구현체를 지정하는 방법은 매우 간단합니다. application.properties
(또는 yml
) 설정 파일에서 간단하게 변경할 수 있었습니다.
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
구현체가 잘 변경되었습니다!
이제 오류없이 잘 동작합니다😁 행복하군요
물리적 명명 전략을 다른 전략으로 변경 때문에 자동으로 스네이크케이스로 변경되지 않습니다. 저는 평소에 항상
@Table
,@Column
으로 명시하기 때문에 괜찮았지만, 자동으로 스네이크케이스로 변경되는 기능을 유용하게 사용하시던 분들은 습관적으로 이름을 명시하지 않는 경우 오류가 발생할 수 있는 점 주의하시면 좋을 것 같습니다!
잘 봤습니다. 좋은 글 감사합니다.