처음 DB Migration을 알게되었을 때 그 필요성을 느끼지 못했다.
단순히 'JPA가 @Table 애너테이션이 사용된 해당 클래스를 기반으로 데이터베이스 테이블을 자동으로 생성해주니 구지 관리까지 해야하나?' 라고 생각했던 것 같다.
DB관리의 필요성을 못느꼈던 것은, 우리 프로젝트가 실제 운용되지 않을 프로젝트라고 생각했기 때문인데, 메인프로젝트를 디벨럽하면서 앞의 전제를 뒤엎고 '실제로 우리 웹 애플리케이션을 운용한다면 지금의 코드를 어떻게 수정해볼 수 있을까?'를 고민하게 되었다.
데이터베이스의 스키마는 애플리케이션의 수명주기동안 끊임없이 변동될 가능성이 있다. 이에 따라 테이블 구조도 변경된다는 의미다. 이건 DB Migration을 사용하는 이유이기도 하다.
1. 스키마 변경관리
- Migration은 변경 스크립트를 사용하여 데이터베이스 스키마를 버전 관리하고, 변경 사항을 적용하거나 되돌릴 수 있다.
2. 데이터 유지
- 일반적으로 애플리케이션은 배포 과정에서 데이터베이스를 초기화한다. 그러나 Migration을 사용하면 데이터 베이스 스키마 변경과 동시에 데이터가 유지되어 일관성을 지킬 수 있다.
3. 다중 환경 지원
- 개발, 테스트, 프로덕션 등 다양한 환경에서 동일한 스키마를 사용하는 경우, Migration을 사용하여 각 환경에 대한 데이터베이스 스키마를 일관되게 유지할 수 있다.
4. 협업과 배포 과정 개선
- Migration을 사용하면 여러 개발자가 동시에 작업할 때도 데이터베이스 변경 사항을 적절하게 통합할 수 있고, 스크립트를 실행하여 스키마 변경을 자동화할 수 있다.
로컬에서 DB 변경 사항을 추가하여 배포 이 후 알아서 관리하도록 하는 것이 바로 flyway다.
이러한 DB형상관리를 위해 사용하는 유용한 tool이 flyway이다.
Flyway는 데이터베이스 스키마 변경을 추적하고 적용하여 데이터베이스 스키마를 최신 상태로 유지할 수 있도록 한다. git관리와 비슷한 것 같기도(?)
dependencies {
implementation 'org.flywaydb:flyway-mysql' // flyway mysql 의존성 추가
implementation 'org.flywaydb:flyway-core' // flyway 의존성 추가
}
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}
파일명은 flyway의 규칙을 따라야 한다. 초기 테이블 생성은 경로, 파일명이 무조건 동일해야 한다.
#초기 sql생성
resources/db/migration/V1__init.sql
#이 후 버전 추가
V {숫자(버전)}__{설명}. sql
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)
);
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문으로 스키마를 생성하는 규칙이 있다는 이야기를 듣고 수정함.
예를들어 member테이블이 있다고 가정할 때, row값으로 memberId가 아닌 id로 작성해주는 것이 관례이며, 대문자는 사용하지 않는다.
또한 테이블은 생성 시 연결하는 것 보다 버전관리를 위해 alter table을 사용한다.
주석 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);
나는 기존 회원을 'user'로 명명하여 관리를 해왔는데 데이터베이스와 연결하면서 user가 sql예약어이기 때문에 사용이 불가하였다. 그래서 임의로 user엔티티에 @Table의 속성값을 사용하여 name을 member로 지정했다.
그런데 문제는 프로그램이 실행되면서 user테이블의 user_id를 매칭하려고 시도하여 계속 에러가 발생했다.
아마도 user엔티티에서 id로 사용했어야 하는데 userId로 선언했기 때문에 db에서 user.user_user_id로 인식을 한것같다.
그래서 user엔티티의 userId를 id로 변경해주고 이닛파일에서도 user_id -> id로 변경해주었더니 해결되었다!
또한 실행할 때는 기존의 테이블과 히스토리를 삭제한 뒤 리프레쉬를 해주어야 제대로 실행된다.