스프링부트 초기 데이터 설정 방법에 대한 정리
스프링부트에서는 데이터베이스 초기 설정을 위해 기본적으로 script(schema.sql, data.sql) 파일을 사용할 수 있는데요.
관념적으로 데이터 정의어(DDL)는 schema.sql 파일에 작성하고 데이터 조작어(DML)은 data.sql 파일에 작성됩니다.
스크립트 파일의 동작은 spring boot 2.4.x 버전과 2.5.x 버전을 기점으로 차이가 있기 때문에 동작 방식에 대해서 알고 적용할 필요가 있는데요.
(스트립트 파일을 적용하는 것의 상위 방식으로는 Flyway 또는 Liquibase라고 하는 마이그레이션 도구가 있습니다.)
아래 내용은 Spring Boot 2.4.x 이하 버전과 2.5.x 이상 버전을 기준으로 스프링 부트에서 script 파일과 hibernate 초기화가 되는 순서에 대해 정리한 내용으로 사용하시는 스프링 부트 버전을 참고하여 봐주시면 좋을 것 같습니다.
***
내용에 앞서 가장 중요한 점은 스크립트 기반의 초기화화 Hibernate 기반의 초기화가 함께 사용되는 것은 권장되지 않는다는 것입니다.
(마찬가지로 Flyway, Liquibase와 script 방식이 함께 사용되는 것 역시 권장되지 않으며, 향후 지원이 제거될 예정입니다.)
application.properties (or application.yml) 설정 옵션
// spring boot 2.4.x
spring.datasource.initialization-mode=always
// spring boot 2.5.x
spring.sql.init.mode=always
// script 파일이 hibernate 초기화 이후 동작하게 하기 위한 옵션
spring.jpa.defer-datasource-initialization=true
버전별 차이를 알아보기에 앞서 application.properties(또는 .yml)에 공통적으로 적용되는 옵션에 대해서 먼저 살펴보겠습니다.
먼저 'spring.datasource.initialization-mode' 옵션입니다.
스프링의 경우 내장 데이터베이스(H2, HSQL, Derdy)에 대해서 스크립트 기반의 초기화를 기본적으로 실행하지만, 내장 데이터베이스가 아닌 경우 다음 옵션을 활성화(= always) 해야 하는데요.
때문에 사용하시는 데이터베이스가 내장 데이터베이스가 아니라 외부 데이터베이스와 연결되어 있다면 다음 옵션을 활성화해야 script 파일이 적용됩니다.
'spring.datasource.initialization-mode' 옵션은 spring boot 2.5.x 버전부터 Deprecated 되었으며 'spring.sql.init.mode' 옵션으로 사용된다는 점은 알아두어야 하는 부분입니다.
/*
해당 옵션에 적용되는 값으로는 모든 데이터베이스에 sql script를 동작시키는 'always', embedded(내장) 데이터베이스만 sql 스크립트를 동작시키는 'embedded', 그리고 모든 데이터베이스에 sql script를 동작시키지 않는 'never'가 있습니다.
*/
이어서 'spring.jpa.defer-datasource-initialization' 옵션입니다.
앞에서 이야기한 것처럼 hibernate 기반의 초기화와 script 기반의 초기화가 함께 사용되는 것은 권장되지 않는 방법인데요.
만약 어쩔 수 없는 경우로 두 가지가 함께 사용되어야 할 때, 특히 spring boot 2.5.x 버전 이후에서 필수적인 옵션입니다.
(아래 예시에서 자세하게 살펴볼 것이지만 스프링부트 2.5.x 버전부터의 동작 방식은 script 파일 실행 이후 hibernate가 초기화되는데 hibernate 초기화 이후 script 파일을 동작시키기 위한 옵션입니다.)
이어서 본격적으로 스프링부트 버전에 따른 script 데이터 초기화 방식의 경우의 수를 살펴볼 텐데요.
script 파일의 동작되는 순서와 'spring.jpa.hibernate.ddl-auto' 옵션으로 인해 적용되는 hibernate 초기화 순서에 주의해서 봐주시면 좋을 것 같습니다.
Entity 및 schema.sql, data.sql
@Table(name = "member")
@Entity
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String password;
}
(Member Entity)
DROP TABLE IF EXISTS member;
CREATE TABLE `member` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'test',
`name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
`password` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
(schema.sql)
INSERT INTO member (name, password)
VALUES ('test1', 'password1'),
('test2', 'password2');
(data.sql)
Spring Boot 2.4.x 이하 버전
// JPA Setting
spring.jpa.hibernate.ddl-auto=create
// DataSource Setting
spring.datasource.initialization-mode=always
1. ddl-auto=create이며 schema.sql, data.sql 둘 다 존재할 때
schema.sql -> data.sql -> ddl-auto=create
(결과는 data.sql의 insert data 적용되지 않음)
2. ddl-auto=create이며 data.sql만 존재할 때
ddl-auto=create -> data.sql
(결과는 data.sql의 insert data 적용)
3. ddl-auto=none이고 schema.sql, data.sql 모두 있을 때
schema.sql -> data.sql
(결과는 data.sql의 insert data 적용)
4. ddl-auto=none이고 data.sql만 있을 때
(table이 생성되지 않고, 따라서 데이터도 없음)
jpa를 사용하는 spring boot 2.4.x 이하 버전에서는 최초 테이블이 생성되기 전에 schema.sql, data.sql 파일이 있고 spring.jap.hibernate.ddl-auto 옵션의 값이 none이 아닌 create 또는 update, create-drop의 경우 결과를 확인했을 때 data.sql에서 입력한 데이터가 존재하지 않는 것을 확인할 수 있는데요.
이유는 script 실행 이후 ddl-auto 옵션에 대한 값이 적용되기 때문이며 'create', 'update', 'create-drop'으로 인해 script로 생성된 테이블이 drop 되고 새로 생성되기 때문입니다.
(테이블이 없는 상태에서 ddl-auto=none 옵션을 설정하고 schema.sql 파일 없이 data.sql 파일만으로 데이터를 넣으려고 하면 테이블 자체가 생성되지 않습니다.)
Spring Boot 2.5.x 이상 버전
// JPA Setting
spring.jpa.hibernate.ddl-auto=none
// SQL Init
spring.sql.init.mode=always
앞에서 언급한 것처럼 스프링 부트 2.5.x 버전부터는 spring.datasource.initialization-mode 옵션이 Deprecated 되었습니다. 따라서 해당 버전부터는 spring.sql.init.mode 옵션을 적용해 줘야 스크립트 파일이 정상적으로 적용됩니다.
1. ddl-auto=create이고 schema.sql, data.sql 모두 있을 때
schema.sql -> data.sql -> ddl-auto=create
(결과는 2.4.x 버전과 마찬가지로 data.sql의 실행 데이터가 적용되지 않습니다.)
spring.jpa.defer-datasource-initialization=ture
하지만 2.5.x 버전부터는 application.properties 파일에 다음 설정을 사용할 수 있는데요.
해당 설정을 사용하게 되면 ddl-auto 옵션에 대한 설정이 먼저 실행되고 이후 script가 실행되어 data.sql의 쿼리가 적용되게 됩니다.
2. ddl-auto=create이고 data.sql만 있을 경우
data.sql -> ddl-auto=create
(결과는 SQLSyntaxErrorException: Table 'member' doesn't exist 발생)
여기서도 마찬가지로 spring.jpa.defer-datasource-initialization=true 설정을 추가하면 ddl-auto 실행 이후 data.sql이 실행되면서 데이터가 적용되게 됩니다.
3. ddl-auto=none이고 schema.sql, data.sql 모두 있을 때
schema.sql -> data.sql
(정상적으로 실행되며 ddl-auto=none 이기 때문에 defer 옵션과는 관계가 없습니다.)
4. ddl-auto=none이고 data.sql만 있을 때
SQLSyntaxErrorException: Table 'member' doesn't exist
해당 내용은 연결된 mysql의 general_log 파일을 통해 요청되는 쿼리 순서를 파악하며 정리한 내용입니다.
잘못된 내용이 있을 경우 댓글 남겨주시면 다시 확인하여 답변드리도록 하겠습니다. 감사합니다.
< 참고 자료 >
https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.data-initialization
https://www.baeldung.com/spring-boot-data-sql-and-schema-sql
'Programming > Spring Boot' 카테고리의 다른 글
Spring Event, @TransactionalEventListener 사용하기 (6) | 2023.03.16 |
---|---|
(Spring Security + JWT) Refresh Token을 통한 토큰 재발급에 대해서 (0) | 2023.03.12 |
@Scheduled 동작 시 timezone 설정 관련하여 발생한 이슈 정리 (0) | 2023.01.06 |
@Transactional 상태에서 Exception이 발생했을 때 Rollback 동작 과정 (0) | 2022.12.27 |
spring 이벤트 사용하기(event publisher, event listener) (2) | 2022.12.23 |