ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Schema-validation: missing table [name] 에러
    개발/JPA & Hibernate 2022. 3. 31. 03:43

     

    결론부터 보기

     

    현재 MySQL 5.7, MariaDB JDBC Driver 그리고 Spring Data JPA (Hibernate 5.67) 을 사용하고 있다.

    Spring Data JPA 를 사용하다보면 hibernate ddl-auto 라는 옵션을 사용할 수 있다.

    옵션에는 validate, create, create-drop, update, none 등이 있다.

    그리고 현재 validate 옵션을 사용 중이다.

    validate : 서버를 기동할 때 JPA Entity 클래스와 DB 스키마를 비교하여 [테이블, 컬럼, id generator] 가 유효한지 검사하는 옵션이다.

    문제 현상

    분명 테이블이 있는데, 테이블이 없다고 한다.

    validate 옵션을 설정해놓아서 서버를 기동하니 schema validation 이 진행된다.

    그런데 다음과 같은 에러가 발생했다.

    .... Schema-validation: missing table [players]

    아무리 눈을 씻고 보아도 players 테이블은 존재했다.

    또, ddl-auto 옵션을 update 로 변경해보았더니, 이미 players 테이블이 존재한다는 메시지가 출력되었다.

    확실히 이상했다.

    1차 디버깅 (대소문자 문제)

    문제가 발생한 곳으로 이동해보니 다음과 같이 tableInformation 이 null 이라 발생한 예외다.

    tableInformation 을 추적해보니 tables 를 통해서 가져오고 있었다. (테이블들의 정보를 담고 있는 변수)

    tables 를 어떻게 가져오는지 알기위해 쭉 따라 들어갔다. (databaseInformation.getTablesInformation(...) 로 이동)

    )

    extractionContext.getJdbcDatabaseMetaData().getTables(...) 로 이동

    DB 테이블의 스키마 정보를 얻기 위해서 catalog(데이터베이스 이름) 을 이용해서 meta 정보를 조회하는 쿼리를 만든다는 사실을 알게되었다.

    다음과 같은 url 로 DB에 연결한다고 가정하자.
    jdbc:mysql://localhost:5430/football_team
    -> football_team 이 데이터베이스 이름

    그런데 여기서 유의할 점은 catalog 가 upper case (대문자) 로 저장되어 있다는 사실이다.

    1차적인 문제점은 파악이 되었다.

    • MySQL 기본설정에 따르면 대소문자를 구분해서 disk 에 DB 또는 테이블을 저장하고 비교한다.
      (Unix 기준 lower_case_table_names = 0 이 default. 공식문서 참고)
    • 디버깅 결과 대문자 이름으로 DB를 조회하고 있다.
    • 그러나 실제 DB 이름은 소문자로 되어있어서 조회가 안된다.

    2차 디버깅 (hibernate 의 대소문자 정책 문제)

    여기서 생기는 궁금증은 jdbc url에 소문자로 기입했음에도 왜 catalog에는 대문자로 DB 이름을 저장하는지 이다.

    그래서 문제의 catalog 가 어떻게 만들어졌는지 역추적 해보았다.

    아까와 비슷한 방법으로 추적하다보니..

    unquotedCaseStrategy 에 따라서 데이터베이스 이름의 대소문자를 정한다는 것을 알 수 있었다.

    주석에도 적혀있듯 default 가 upper case 이다.

    하지만 나는 upper case (대문자)를 원하지 않으므로 이 설정을 바꿀 방법을 찾기 위해서 좀 더 들어갔다.

    우선 위에 클래스 (NormalizingIdentifierHelperImpl) 를 생성하는 곳으로 이동했다.

    IdentifierHelperBuilder 에서 생성하고 있었다.

    unqoutedCaseStrategy 값을 역추적해보니, metaData.storesLowerCaseIdentifieres() 에 따라 결정되는 것을 알게 되었다.

    metaData.storesLowerCaseIdentifiers() 를 타고 들어가서 보니,

    커넥션 객체의 getLowercaseTableNames() 에 의해 결정된다는 사실을 알게되었다.

    (리턴 값이 1이면 내가 원하는대로 소문자를 얻을 수 있다.)

    마지막으로, 열쇠가 되는 lowercaseTableNames 라는 값을 어떤 쿼리로 조회하는지 알게되었다.

    MySQL에 있는 @@lower_case_table_names 변수를 조회하고 있다.

    저 변수의 값이 1이 되어야 hibernate 가 소문자로 취급하는데, 현재 로컬 MySQL 기본값이 0 으로 되어있어서 대문자로 가져온 것이다.

    결론

    상황을 정리하자면

    • hibernate 설정 (ddl-auto : validate) 때문에 유효성 검사를 한다.
    • (Unix기준) MySQL 기본 설정때문에 대소문자를 구분하여 조회하고 있다.
    • DB 이름이 소문자인데, Hibernate 가 자꾸 이것을 대문자로 변환한다. (default)
    • 그 이유는 Hibernate가 MySQL 의 @@lower_case_table_names 의 설정을 따라가기 때문이다.

     

    Missing Table 문제를 해결하려면

    • 데이터베이스에 테이블이 존재하는지 확인한다.
    • MySQL @@lower_case_table_names 설정을 확인한다.
      • @@lower_case_table_names 를 1로 설정하면
      • DB나 테이블을 생성하면 low case 로 생성된다.
      • hibernate 에서도 소문자로 읽어들인다.

     

    @@lower_case_table_names 값 변경 방법

    설정하는 방법은 /etc/mysql/conf.d 파일을 수정하면 된다.

    [mysqld] lower_case_table_names = 1

     

    댓글

Designed by Tistory.