Skip to main content

Overview

The Blackjack API uses Flyway for database schema migrations. Flyway provides version-controlled, repeatable database migrations that ensure your MySQL schema is always in a consistent state.

Why Flyway?

Flyway is used alongside R2DBC because:
  • R2DBC limitation: R2DBC doesn’t support DDL operations (schema creation)
  • JDBC for migrations: Flyway uses JDBC for reliable schema management
  • R2DBC for queries: Application uses R2DBC for reactive data access
  • Separation of concerns: Schema management separate from data access

Dependencies

Flyway is configured in pom.xml:
<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-core</artifactId>
</dependency>
<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-mysql</artifactId>
</dependency>
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>

Flyway Configuration

Local Development

Configure Flyway in application-local.yml:
spring:
  flyway:
    enabled: true
    url: jdbc:mysql://localhost:3307/blackjack
    user: blackjack
    password: blackjack
    locations: classpath:db/migration

Docker Environment

For Docker deployments, use service names in application-docker.yml:
spring:
  flyway:
    enabled: true
    url: jdbc:mysql://mysql:3306/blackjack
    user: blackjack
    password: blackjack
    locations: classpath:db/migration

Production Environment

For production, use environment variables in application-prod.yml:
spring:
  flyway:
    enabled: ${SPRING_FLYWAY_ENABLED:true}
    url: ${SPRING_FLYWAY_URL}
    user: ${SPRING_FLYWAY_USER}
    password: ${SPRING_FLYWAY_PASSWORD}
    locations: classpath:db/migration

Configuration Properties

PropertyDescriptionExample
spring.flyway.enabledEnable/disable Flywaytrue
spring.flyway.urlJDBC connection URL (not R2DBC)jdbc:mysql://localhost:3307/blackjack
spring.flyway.userDatabase usernameblackjack
spring.flyway.passwordDatabase passwordblackjack
spring.flyway.locationsMigration scripts locationclasspath:db/migration
Important: Flyway uses JDBC URLs (jdbc:mysql://), not R2DBC URLs (r2dbc:mysql://).

Migration Scripts

Migration scripts are stored in src/main/resources/db/migration/.

Naming Convention

Flyway follows a strict naming convention:
V{version}__{description}.sql
  • V: Version prefix (required)
  • : Version number (e.g., 1, 2, 1.1, 2.5)
  • __: Double underscore separator (required)
  • : Human-readable description
  • .sql: File extension

Example Migrations

V1__create_players.sql
CREATE TABLE players (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  external_id VARCHAR(36) NOT NULL UNIQUE,
  name VARCHAR(30) NOT NULL UNIQUE,
  wins INT NOT NULL,
  losses INT NOT NULL
);
This migration creates the initial players table with:
  • Auto-incrementing primary key
  • UUID-based external ID for API references
  • Unique player names (1-30 characters)
  • Win and loss counters

Migration Lifecycle

Application Startup

  1. Flyway initializes: Checks for flyway_schema_history table
  2. Detects pending migrations: Compares filesystem with database
  3. Executes migrations: Runs pending migrations in version order
  4. Records success: Updates flyway_schema_history table
  5. Application starts: Only after migrations succeed

Migration States

StateDescription
PendingMigration exists but hasn’t run
SuccessMigration completed successfully
FailedMigration failed (blocks startup)
IgnoredMigration skipped (e.g., missing)

Creating New Migrations

Step 1: Create Migration File

Create a new SQL file in src/main/resources/db/migration/:
touch src/main/resources/db/migration/V2__add_player_email.sql

Step 2: Write Migration SQL

ALTER TABLE players
ADD COLUMN email VARCHAR(255) UNIQUE;

Step 3: Test Migration

Restart the application. Flyway will automatically detect and run the new migration.

Step 4: Verify Migration

Check the flyway_schema_history table:
SELECT * FROM flyway_schema_history ORDER BY installed_rank;

Schema History Table

Flyway maintains a flyway_schema_history table:
CREATE TABLE flyway_schema_history (
  installed_rank INT NOT NULL,
  version VARCHAR(50),
  description VARCHAR(200),
  type VARCHAR(20),
  script VARCHAR(1000),
  checksum INT,
  installed_by VARCHAR(100),
  installed_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  execution_time INT,
  success BOOLEAN,
  PRIMARY KEY (installed_rank)
);
This table tracks:
  • Which migrations have run
  • When they ran
  • Whether they succeeded
  • Checksums to detect changes

Best Practices

1. Never Modify Executed Migrations

Once a migration has been applied to any environment, never modify it. Create a new migration instead.
Bad:
-- Modifying V1__create_players.sql after it's been deployed
ALTER TABLE players ADD COLUMN email VARCHAR(255);
Good:
-- Creating V2__add_player_email.sql
ALTER TABLE players ADD COLUMN email VARCHAR(255);

2. Keep Migrations Small

Create focused migrations that do one thing:
-- V2__add_player_email.sql
ALTER TABLE players ADD COLUMN email VARCHAR(255) UNIQUE;

-- V3__add_player_timestamps.sql  
ALTER TABLE players ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE players ADD COLUMN updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;

3. Use Descriptive Names

Migration names should clearly describe what they do:
  • V1__create_players.sql (good)
  • V1__initial.sql (bad - not descriptive)
  • V2__add_player_email_column.sql (good)
  • V2__update.sql (bad - too vague)

4. Test Migrations Locally

Always test migrations in local environment before deploying:
  1. Run migration locally
  2. Verify schema changes
  3. Test application functionality
  4. Check rollback strategy (if needed)

5. Include Indexes

Create indexes for frequently queried columns:
-- V1__create_players.sql
CREATE TABLE players (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  external_id VARCHAR(36) NOT NULL UNIQUE,
  name VARCHAR(30) NOT NULL UNIQUE,
  wins INT NOT NULL,
  losses INT NOT NULL,
  INDEX idx_wins (wins),
  INDEX idx_losses (losses)
);

6. Document Complex Migrations

Add comments for complex schema changes:
-- V4__optimize_player_ranking.sql
-- Add composite index to optimize ranking query
-- See: MySqlPlayerRepositoryAdapter.findRanking()
CREATE INDEX idx_player_ranking 
ON players (wins DESC, losses ASC, name ASC);

Rollback Strategies

Flyway supports rollbacks in paid versions. For community edition:

Manual Rollback

Create compensating migrations:
-- V3__remove_player_email.sql
ALTER TABLE players DROP COLUMN email;

Backup and Restore

For critical migrations, backup before applying:
# Backup database
mysqldump -u blackjack -p blackjack > backup_before_v3.sql

# Apply migration
# (Flyway runs automatically on startup)

# Restore if needed
mysql -u blackjack -p blackjack < backup_before_v3.sql

Troubleshooting

Migration Failed

If a migration fails:
  1. Check logs: Review error message in application logs
  2. Fix SQL: Correct the SQL syntax or logic error
  3. Clean failed migration:
    DELETE FROM flyway_schema_history WHERE success = 0;
    
  4. Retry: Restart application to re-run migration

Checksum Mismatch

If Flyway detects a modified migration:
Migration checksum mismatch for migration version 1
Solution: Never modify executed migrations. Create a new migration instead.

Out of Order Migrations

If migrations are executed out of order:
spring:
  flyway:
    out-of-order: true  # Allow out-of-order migrations
Only enable out-of-order in development. Keep disabled in production.

Flyway Commands

While Flyway runs automatically on startup, you can also use Maven plugin:
<plugin>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-maven-plugin</artifactId>
    <version>10.0.0</version>
</plugin>
Common commands:
# Check migration status
mvn flyway:info

# Apply migrations
mvn flyway:migrate

# Validate migrations
mvn flyway:validate

# Repair metadata (use with caution)
mvn flyway:repair

Production Considerations

1. Disable Flyway for Some Deployments

For environments where schema is managed separately:
spring:
  flyway:
    enabled: false

2. Separate Migration Credentials

Use different credentials for migrations vs. application:
spring:
  flyway:
    user: flyway_admin  # Admin user for schema changes
    password: ${FLYWAY_PASSWORD}
  
  r2dbc:
    username: app_user  # Limited user for data access
    password: ${R2DBC_PASSWORD}

3. Migration Timeouts

For long-running migrations:
spring:
  flyway:
    connect-retries: 3
    lock-retry-count: 10

Build docs developers (and LLMs) love