문제
Test Code를 돌릴 때 liquibase에서 아래와 같이 'DATABASECHANGELOG" already exists; SQL statement' 라는 에러가 났다.
liquibase로 changelog 중 schema.xml을 만들 때 DATABASECHANGELOG라는 테이블을 포함하지 않았고,
생성된 schema.xml에도 create하는 부분이 없는데 DATABASECHANGELOG 생성을 시도하고 있었다.
원인
DATABASECHANGELOG는 liquibaes에서 사용하고 있는 테이블이고, 없으면 liquibase에서 자동으로 생성한다.
liquibase에서 DATABASECHANGELOG 테이블을 만들 때, 테이블이 있는 지 없는 지 조사한다.
조사할 때 이미 존재했던 DATABASECHANGELOG 테이블을 발견하지 못하고 다시 생성하려 한 것을 알 수 있다.
해결 방법을 찾아보니 아래와 같이 DATABASE_TO_UPPER=false를 h2 db 설정에서 지우라고 했다.
애플리케이션 h2 db에 설정된 것을 확인해보니 DATABASE_TO_UPPER=false가 있었다.
datasource:
type: com.zaxxer.hikari.HikariDataSource
url: jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;DATABASE_TO_UPPER=FALSE;MODE=MYSQL
username:
password:
hikari:
auto-commit: false
h2 database의 기본 옵션은 테이블이나 컬럼 이름을 모두 대문자로 생성하는 것이다. 그럼 h2 database 내부적으로 대소문자 구분할 필요가 없기 때문이다. DATABASE_TO_UPPER=false 옵션은 모두 대문자로 만들지 않고, 테이블이나 컬럼을 대소문자로 구분해서 만든다.
DATABASE_TO_UPPER=false 옵션을 넣음으로써, liquibase는 DATABASECHANGELOG가 소문자일 거라 예상하고 소문자인 databasechangelog로 찾는다. 하지만 로그를 보면 liquibase는 생성할 때 DATABASECHANGELOG를 대문자로 생성한다.
내 애플리케이션은 liquibase를 실행할 때 changelog로 schema.xml과 data.xml 2개를 포함한다.
liquibase는 chagelog를 db에 반영하는 작업을 2번 실행하고 이 때마다 DATABASECHANGELOG 테이블 존재 확인 및 생성을 한다.
즉, 에러가 난 과정은 아래와 같다.
1. schema.xml을 실행한다. 이 때 DATABASECHANGELOG로 대문자로 테이블에 생성된다.
2. data.xml을 실행한다. h2 database의 DATABASE_TO_UPPER=false 옵션으로 인해 databasechangelog로 찾는다. 하지만 생성된 테이블은 DATABASECHANGELOG 대문자라 찾을 수 없다.
3. 다시 DATABASE_TO_UPPER로 테이블 생성을 시도한다.
4. DATABASECHANGELOG" already exists; SQL statement 에러가 발생한다.
해결
첫 번째 방법: DATABASE_TO_UPPER=false 옵션 제거
DATABASE_TO_UPPER=false 옵션을 제거하면 모두 대문자로 테이블이 만들어지므로 DATABASECHANGELOG 에러는 사라졌다.
하지만 애플리케이션이 JOOQ를 사용하고 있고, JOOQ로 실행되는 sql 쿼리는 테이블이 소문자이다. H2 Database는 대소문자를 구분하므로 쿼리에 소문자로 된 테이블명은 찾을 수 없다는 에러가 떴다.
따라서, application-test.yml에 h2 db 옵션에서 DATABASE_TO_UPPER=false을 제거하고 2개 옵션을 추가했다.
- DATABASE_TO_LOWER=TRUE: 모든 테이블명과 컬럼명을 소문자로 생성한다.
- CASE_INSENSITIVE_IDENTIFIERS=TRUE: 대소문자 구분을 하지 않는다.
datasource:
type: com.zaxxer.hikari.HikariDataSource
## 변경 전
## url: jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;DATABASE_TO_UPPER=FALSE;MODE=MYSQL
url: jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;DATABASE_TO_LOWER=TRUE;CASE_INSENSITIVE_IDENTIFIERS=TRUE;MODE=MYSQL
username:
password:
hikari:
auto-commit: false
두 번째 방법: Liquibase에 database-change-log-table, database-change-log-lock-table 설정
DATABASE_TO_UPPER=false 옵션을 제거하지 않고, liquibase에서 생성하는 DATABASECHANGELOG 테이블 이름을 소문자로 지정해줄 수 있다.
application-test.yml에 liquibase 설정을 아래와 같이 테이블명을 소문자로 해준다.
liquibase:
change-log: classpath:config/liquibase/master.xml
database-change-log-lock-table: 'databasechangeloglock'
database-change-log-table: 'databasechangelog'
그러면 소문자로 테이블을 생성하고, 찾을 때도 소문자로 찾기 때문에 DATABASECHANGELOG" already exists; SQL statement 에러가 발생하지 않는다.
참고 자료
https://docs.liquibase.com/concepts/tracking-tables/databasechangelog-table.html