[DB Migration] 메인프로젝트 코드리팩토링(4)-flyway tool 적용하기

박두팔이·2023년 6월 28일
0

DB Migration의 필요성?

처음 DB Migration을 알게되었을 때 그 필요성을 느끼지 못했다.

단순히 'JPA가 @Table 애너테이션이 사용된 해당 클래스를 기반으로 데이터베이스 테이블을 자동으로 생성해주니 구지 관리까지 해야하나?' 라고 생각했던 것 같다.

DB관리의 필요성을 못느꼈던 것은, 우리 프로젝트가 실제 운용되지 않을 프로젝트라고 생각했기 때문인데, 메인프로젝트를 디벨럽하면서 앞의 전제를 뒤엎고 '실제로 우리 웹 애플리케이션을 운용한다면 지금의 코드를 어떻게 수정해볼 수 있을까?'를 고민하게 되었다.

데이터베이스의 스키마는 애플리케이션의 수명주기동안 끊임없이 변동될 가능성이 있다. 이에 따라 테이블 구조도 변경된다는 의미다. 이건 DB Migration을 사용하는 이유이기도 하다.

💡 DB Migration의 장점 (4가지)

  • 1. 스키마 변경관리
    - Migration은 변경 스크립트를 사용하여 데이터베이스 스키마를 버전 관리하고, 변경 사항을 적용하거나 되돌릴 수 있다.

  • 2. 데이터 유지
    - 일반적으로 애플리케이션은 배포 과정에서 데이터베이스를 초기화한다. 그러나 Migration을 사용하면 데이터 베이스 스키마 변경과 동시에 데이터가 유지되어 일관성을 지킬 수 있다.

  • 3. 다중 환경 지원
    - 개발, 테스트, 프로덕션 등 다양한 환경에서 동일한 스키마를 사용하는 경우, Migration을 사용하여 각 환경에 대한 데이터베이스 스키마를 일관되게 유지할 수 있다.

  • 4. 협업과 배포 과정 개선
    - Migration을 사용하면 여러 개발자가 동시에 작업할 때도 데이터베이스 변경 사항을 적절하게 통합할 수 있고, 스크립트를 실행하여 스키마 변경을 자동화할 수 있다.

💡 DB Migration을 사용하지 않은 경우 예상되는 문제?

  • 예를 들어 우리프로젝트가 배포가 다 된 상태에서 배포 후 기능을 추가하기 위해 스키마를 변경했다면 매번 배포 서버DB에 들어가서 테이블을 수정해야 한다. 이런경우 굉장히 귀찮다. 번거롭고 휴먼에러가 발생하기 좋다.

로컬에서 DB 변경 사항을 추가하여 배포 이 후 알아서 관리하도록 하는 것이 바로 flyway다.


Flyway

이러한 DB형상관리를 위해 사용하는 유용한 tool이 flyway이다.

Flyway는 데이터베이스 스키마 변경을 추적하고 적용하여 데이터베이스 스키마를 최신 상태로 유지할 수 있도록 한다. git관리와 비슷한 것 같기도(?)

의존성추가 : build.gradle

dependencies {
	implementation 'org.flywaydb:flyway-mysql' // flyway mysql 의존성 추가
	implementation 'org.flywaydb:flyway-core' // flyway 의존성 추가
}

application.yml 설정 추가

spring:
  flyway:
    #location: classpath:db/migration 스키마 defaults 경로
    enabled: true #flyway 활성화, default로 true 이지만 명시적으로 작성
    baseline-on-migrate: true #flyway 변경 이력 테이블 생성 여부
    
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: ${url} 
    username: ${username}
    password: ${userpassword}

init 파일 추가 : resources/db/migration/V1__init.sql

파일명은 flyway의 규칙을 따라야 한다. 초기 테이블 생성은 경로, 파일명이 무조건 동일해야 한다.

#초기 sql생성
resources/db/migration/V1__init.sql

#이 후 버전 추가
V {숫자(버전)}__{설명}. sql


sql문 작성 (user 테이블 예시) -> 수정 됨 아래글 참고!!🚨

CREATE TABLE IF NOT EXISTS member (
    userId 				BIGINT AUTO_INCREMENT PRIMARY KEY,
    uuid 				VARCHAR(255),
    email 				VARCHAR(255) NOT NULL UNIQUE,
    password 			VARCHAR(255) NOT NULL,
    name 				VARCHAR(10) NOT NULL,
    phone 				VARCHAR(255) UNIQUE,
    roles 				VARCHAR(255),
    userStatus 			VARCHAR(20) NOT NULL,
    profileImage 		TEXT,
    isDelete 			BOOLEAN DEFAULT FALSE,
    createdAt     		TIMESTAMP,
    lastModifiedDate  	TIMESTAMP,
    createdBy     		varchar(255),
    modifiedBy     		varchar(255)
    );

참고: User 엔티티 클래스

public class User extends Auditable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long userId;
    
    @Column
    private String uuid = UUID.randomUUID().toString();
    
    @Column(nullable = false, unique = true)
    private String email;
    
    @Column(nullable = false)
    private String password;
    
    @Column(length = 10, nullable = false)
    private String name;
    
    @Column(unique = true)
    private String phone;

    // 역할
    @ElementCollection(fetch = FetchType.EAGER)
    private List<String> roles = new ArrayList<>();

    // 회원상태
    @Enumerated(value = EnumType.STRING)
    @Column(length = 20, nullable = false)
    private UserStatus userStatus = UserStatus.USER_ACTIVE;

    // 프로필사진
    @Lob
    @Column(name = "profileImage")
    private String profileImage;

    // 삭제여부
    @Column
    private boolean isDelete = false;

    @OneToMany(mappedBy = "user", cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
    private List<Cv> cvs = new ArrayList<>();
}

추가설명

sql문으로 스키마를 생성하는 규칙이 있다는 이야기를 듣고 수정함.

  1. 예를들어 member테이블이 있다고 가정할 때, row값으로 memberId가 아닌 id로 작성해주는 것이 관례이며, 대문자는 사용하지 않는다.

    • sql문: 스네이크 케이스 (ex. user_id)
    • java: 카멜케이스 (ex. userId)
  2. 또한 테이블은 생성 시 연결하는 것 보다 버전관리를 위해 alter table을 사용한다.

  3. 주석 no~no~ (가끔 에러남..)

⬇️ 수정 된 sql (1) - table 생성 ⬇️

create table if not exists cv
(
    cv_id 					bigint auto_increment,
    title 					varchar(255) not null,
    email 					varchar(255),
    name 					varchar(255),
    address 				varchar(255),
    phone 					varchar(255),
    self_introduction 		text,
    development_job 		varchar(255),
    image_url 				text,
    birth_year 				varchar(255),
    birth_month 			varchar(255),
    birth_day 				varchar(255),
    is_delete 				boolean default false,
    created_at     			timestamp,
    last_modified_date  	timestamp,
    created_by     			varchar(255),
    modified_by     		varchar(255),
    user_id 				bigint, ---------------------- 아래 alter tabel과 연결 
    constraint pk_cv_cv_id primary key (cv_id) -------- 제약조건 pk 설정 (고유키)
);

⬇️ 수정 된 sql (2) - alter table 추가 ⬇️

alter table cv
    add constraint fk_cv_user_id
        foreign key (user_id) references member(user_id);

flyway로 인한 에러발생

나는 기존 회원을 'user'로 명명하여 관리를 해왔는데 데이터베이스와 연결하면서 user가 sql예약어이기 때문에 사용이 불가하였다. 그래서 임의로 user엔티티에 @Table의 속성값을 사용하여 name을 member로 지정했다.

그런데 문제는 프로그램이 실행되면서 user테이블의 user_id를 매칭하려고 시도하여 계속 에러가 발생했다.
아마도 user엔티티에서 id로 사용했어야 하는데 userId로 선언했기 때문에 db에서 user.user_user_id로 인식을 한것같다.

그래서 user엔티티의 userId를 id로 변경해주고 이닛파일에서도 user_id -> id로 변경해주었더니 해결되었다!

또한 실행할 때는 기존의 테이블과 히스토리를 삭제한 뒤 리프레쉬를 해주어야 제대로 실행된다.

profile
기억을 위한 기록 :>

0개의 댓글