Spring에서 초기 테이블과 데이터 관리를 위해서 data.sql과 schema.sql을 사용하였다. 하지만 테이블 스키마가 변경되거나 필수로 초기에 들어가야하는 데이터들이 추가되거나 수정되었을 때 히스토리 관리가 잘 되지 않았다. 

특히 서로 교류가 잘 되지 않은 경우에서는 컬럼이 추가되거나 무엇이 변경되었는지 알지 못해서 문제를 유발할 수 있기에 이를 관리 할 수 있는 무언가가 필요했다.

그래서 Redgate에서 제공하는 Flyway를 사용해보기로 했다. 우선 내 개인 프로젝트인 timeline에 적용시켜봤다.

 

데이터베이스 버전관리 Flyway

https://flywaydb.org/

동작 방식

Flyway가 버전관리를 하기위해서 테이블이 생성된다. Flyway가 버전관리는 이 테이블에 데이터베이스의 상태를 기록하면서 진행한다. 

Flyway가 시작되면 파일시스템 또는 마이그레이션 대상의 classpath를 스캔해서 Sql 또는 Java로 쓰여진 파일을 찾는다. 이 마이그레이션 작업은 파일에 적혀있는 version number대로 순서대로 진행된다. 그리고 현재 마이그레이션 해야할 파일의 버전과 테이블에 기록된 버전을 확인해보고 같으면 넘어간다.

Flyway에서 사용하는 테이블은 flyway_schema_history로 아래와 같이 구성되어있다.

CREATE TABLE `flyway_schema_history` (
  `installed_rank` int(11) NOT NULL,
  `version` varchar(50) DEFAULT NULL,
  `description` varchar(200) NOT NULL,
  `type` varchar(20) NOT NULL,
  `script` varchar(1000) NOT NULL,
  `checksum` int(11) DEFAULT NULL,
  `installed_by` varchar(100) NOT NULL,
  `installed_on` timestamp NOT NULL DEFAULT current_timestamp(),
  `execution_time` int(11) NOT NULL,
  `success` tinyint(1) NOT NULL,
  PRIMARY KEY (`installed_rank`),
  KEY `flyway_schema_history_s_idx` (`success`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
installed_rank 인덱스
version 버전명 (V나 R 뒤에 붙는 숫자)
description 설명
type SQL 또는 JDBC 
script 스크립트 이름 V1__kdjlkdf.sql
checksum checksum
installed_by 실행 주최자
installed_on 설치된 시간
execution_time 총 실행시간
success 성공여부

 

간단히 말해 변경된 데이터나 테이블 스키마를 적용하기 위해서는 마지막 버전보다 높은 파일을 만들어서 애플리케이션을 구동하면 된다.

 

애플리케이션에 적용

그럼 flyway를 적용하기 위해 gradle에 라이브러리부터 추가해보자.

dependency {
	compile group: "org.flywaydb", name: "flyway-core", version: '5.2.4'
}

그리고 application.yml을 설정하자.

spring:
  flyway:
    enabled: true
    baselineOnMigrate: true
    encoding: UTF-8

그리고 테이블과 데이터를 넣을 sql을 만들자. 

그리고 Springboot 애플리케이션을 실행시키면 해당 테이블에 버전 히스토리가 기록된다.

 

버전관리하기에 좋은거 같다.

엔티티 매핑에서 사용될 컬럼의 필드 유형을 설정하는 매핑 어노테이션을 정리해보자.

@Column
테이블에서 사용 되는 컬럼이라는 필드를 지정해줄때 사용하며 name, nullable(기본이 true) 등의 설정을 해줄 수 있다. 

1
2
@Column(name = "NAME", length = 10, nullable = true)
private String userName;
cs


@Enumerated
자바의 enum 타입을 매핑할 때 사용한다. 속성으로 EnumType.ORDINAL과 EnumType.STRING이 존재하는데 이름 그대로 ORDINAL은 순서를 STRING은 Enum의 이름을 저장한다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Member {
 
  @Id
  @GeneratedValue
  @Column(name = "ID")
  private String id;
 
  @Column(name = "NAME"length = 10, nullable = true)
  private String userName;
 
  // 매핑 정보가 없는 필드
  private int age;
 
  @Enumerated(EnumType.STRING)
  Gender gender;
 
}
 
enum Gender {
  Men, Women;
 
  private Gender() {
 
  }
}
 
// 위와 같이 설정하면 데이터베이스에 Men으로 들어간다.
member.setGender(Gender.Men);
cs


@Temporal
java.util.Date와 java.util.Calendar 값을 매핑 할 때 사용한다.

1
2
3
4
5
6
7
8
9
10
11
// 2018-04-02 형태 (데이터베이스 DATE와 매핑)
@Temporal(TemporalType.DATE)
private Date birthDate;
 
// 12:11:11 (데이터베이스 TIME과 매핑)
@Temporal(TemporalType.TIME)
private Date birthTime;
 
// 2013-10-21 12:11:11 (데이터베이스 TIME과 매핑)
@Temporal(TemporalType.TIMESTAMP)
private Date birthTimeStamp;
cs


@LOB
데이터베이스 BLOB, CLOB 타입과 매핑 된다. CLOB(String, char[], java.sql.CLOB)은 문자, BLOB(byte[], java.sql.BLOB)은 나머지가 매핑된다.

@Transient
저장 조회에 사용되지도 않고 그냥 단순 값을 가지고 있고 싶을때 사용.

1
2
@Transient
private String tempStr;
cs


@Access
데이터베이스에 엔티티에 값이 저장될 때 필드(AccessType.FIELD)의 값을 직접 접근해서 사용할 것인가 아니면 메서드에 직접(AccessType.PROPERTY) 접근할 것 인가를 설정하는 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Access(AccessType.FIELD)
public class Member {
 
  @Id
  @GeneratedValue
  @Column(name = "ID")
  private String id;
 
  @Column(name = "NAME"length = 10, nullable = true)
  private String userName;
 
  // 매핑 정보가 없는 필드
  private int age;
 
  @Enumerated(EnumType.STRING)
  Gender gender;
 
  // 2018-04-02 형태 (데이터베이스 DATE와 매핑)
  @Temporal(TemporalType.DATE)
  private Date birthDate;
 
  // 12:11:11 (데이터베이스 TIME과 매핑)
  @Temporal(TemporalType.TIME)
  private Date birthTime;
 
  // 2013-10-21 12:11:11 (데이터베이스 TIME과 매핑)
  @Temporal(TemporalType.TIMESTAMP)
  private Date birthTimeStamp;
 
  @Transient
  private String tempStr;
 
}
cs


- @Access 필드를 생략하고 @Id 필드를 사용하면 AccessType.FIELD로 설정된 것과 같다.
- 나머지 필드는 @Id를 사용하여 AccessType.FIELD로 사용하고 특정 값만 AccessType.PROPERTY로 설정할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Access(AccessType.FIELD)
public class Member {
 
  @Id
  @GeneratedValue
  @Column(name = "ID")
  private String id;
 
  @Column(name = "NAME"length = 10, nullable = true)
  private String userName;
 
  // 매핑 정보가 없는 필드
  private int age;
 
  @Enumerated(EnumType.STRING)
  Gender gender;
 
  // 2018-04-02 형태 (데이터베이스 DATE와 매핑)
  @Temporal(TemporalType.DATE)
  private Date birthDate;
 
  // 12:11:11 (데이터베이스 TIME과 매핑)
  @Temporal(TemporalType.TIME)
  private Date birthTime;
 
  // 2013-10-21 12:11:11 (데이터베이스 TIME과 매핑)
  @Temporal(TemporalType.TIMESTAMP)
  private Date birthTimeStamp;
 
  @Transient
  private String tempStr;
 
  @Access(AccessType.PROPERTY)
  public String getFullName() {
    return "dbsafer" + this.userName;
  }
 
}
cs


+ Recent posts