Careeroza
Flyway
Setup
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<!-- Flyway 10+: database support is a separate module -->
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-mysql</artifactId> <!-- or flyway-database-postgresql -->
</dependency>Configuration
application.yml
spring:
jpa:
hibernate:
ddl-auto: validate
flyway:
enabled: true # default when flyway-core is on the classpath
locations: classpath:db/migration
baseline-on-migrate: false # true only when adopting Flyway on an existing databaseWriting Migrations
File names follow V<version>__<description>.sql (two underscores). Versions are compared as numbers: V1_1 (1.1) runs before V2, and V10 after V9.
-- V1__create_categories_and_products.sql
CREATE TABLE categories (
id BIGINT PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE
);
CREATE SEQUENCE product_seq START WITH 1 INCREMENT BY 50;
CREATE TABLE products (
id BIGINT PRIMARY KEY,
name VARCHAR(200) NOT NULL,
description TEXT,
price NUMERIC(10,2) NOT NULL,
stock INT NOT NULL DEFAULT 0,
version BIGINT NOT NULL DEFAULT 0,
category_id BIGINT NOT NULL REFERENCES categories(id)
);
CREATE INDEX idx_products_category ON products(category_id);
-- V3__add_product_sku.sql
ALTER TABLE products ADD COLUMN sku VARCHAR(40);
UPDATE products SET sku = CONCAT('SE-', id) WHERE sku IS NULL;
ALTER TABLE products ALTER COLUMN sku SET NOT NULL;
ALTER TABLE products ADD CONSTRAINT uk_products_sku UNIQUE (sku);How Flyway Decides What to Run
Example
A mini Flyway: sort by numeric version, skip what the history table already has, and refuse to start if an applied script was edited (checksum mismatch):
import java.util.*;
import java.util.zip.CRC32;
public class Main {
record Applied(String version, long checksum) {}
static final Map<String, Applied> history = new LinkedHashMap<>(); // flyway_schema_history
static long checksum(String sql) { CRC32 c = new CRC32(); c.update(sql.getBytes()); return c.getValue(); }
static int[] parse(String v) { return Arrays.stream(v.split("\\.")).mapToInt(Integer::parseInt).toArray(); }
static void migrate(Map<String, String> scripts) {
List<String> versions = new ArrayList<>(scripts.keySet());
versions.sort((a, b) -> Arrays.compare(parse(a), parse(b))); // numeric, not alphabetical
for (String v : versions) {
long sum = checksum(scripts.get(v));
Applied done = history.get(v);
if (done == null) {
history.put(v, new Applied(v, sum));
System.out.println(" applied V" + v);
} else if (done.checksum() != sum) {
System.out.println(" ERROR: Validate failed: checksum mismatch for V" + v);
return;
}
}
System.out.println(" schema is at version " + versions.get(versions.size() - 1));
}
public static void main(String[] args) {
Map<String, String> scripts = new HashMap<>(Map.of(
"1", "create table products ...", "2", "create table orders ...", "10", "add index ...", "1.1", "add column stock"));
System.out.println("deploy 1:"); migrate(scripts);
scripts.put("11", "add column sku");
System.out.println("deploy 2:"); migrate(scripts);
scripts.put("2", "create table orders ... -- edited after release!");
System.out.println("deploy 3:"); migrate(scripts);
}
}Output
deploy 1: applied V1 applied V1.1 applied V2 applied V10 schema is at version 10 deploy 2: applied V11 schema is at version 11 deploy 3: ERROR: Validate failed: checksum mismatch for V2
Rules
- Never edit a migration that has run anywhere shared. Write a new one.
- One logical change per file; keep files small and reviewable.
R__name.sql= repeatable migration, re-run whenever its checksum changes (views, functions).- Java-based migrations (
class V5__Backfill implements JavaMigration) for complex data changes. - Never run
flyway cleanagainst production (disable it:spring.flyway.clean-disabled=true, the default in recent versions).