CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-flywaydb--flyway-core

Database migration tool that enables versioned database schema evolution with simple SQL scripts and Java migrations

Pending
Overview
Eval results
Files

java-migrations.mddocs/

Java Migration Development

Framework for creating programmatic database migrations using Java code, providing full access to database connections and configuration for complex migration scenarios that require procedural logic.

Capabilities

JavaMigration Interface

Core contract for Java-based migrations providing version, description, and execution methods.

/**
 * Interface to be implemented by Java-based migrations.
 */
public interface JavaMigration {
    /** Get the version of this migration */
    MigrationVersion getVersion();
    
    /** Get the description of this migration */
    String getDescription();
    
    /** Get the checksum of this migration. Can be null if no checksum is available */
    Integer getChecksum();
    
    /** Whether the execution should take place inside a transaction */
    boolean canExecuteInTransaction();
    
    /** Execute the migration */
    void migrate(Context context) throws Exception;
}

BaseJavaMigration Abstract Class

Base implementation providing default behavior and automatic version extraction from class names.

/**
 * Convenience class for Java-based migrations. Automatically extracts the version and description from the class name.
 */
public abstract class BaseJavaMigration implements JavaMigration {
    /** Get the version from the migration class name */
    public final MigrationVersion getVersion();
    
    /** Get the description from the migration class name */
    public String getDescription();
    
    /** Get the checksum. Default implementation returns null */
    public Integer getChecksum();
    
    /** Whether this migration can execute within a transaction. Default is true */
    public boolean canExecuteInTransaction();
    
    /** Execute the migration - must be implemented by subclasses */
    public abstract void migrate(Context context) throws Exception;
}

Usage Examples:

// Version and description extracted from class name: V2_1__Create_user_table
public class V2_1__Create_user_table extends BaseJavaMigration {
    @Override
    public void migrate(Context context) throws Exception {
        try (Statement statement = context.getConnection().createStatement()) {
            statement.execute("CREATE TABLE users (id SERIAL PRIMARY KEY, name VARCHAR(100))");
        }
    }
}

// Custom checksum and transaction control
public class V3__Complex_migration extends BaseJavaMigration {
    @Override
    public Integer getChecksum() {
        return 12345; // Custom checksum for this migration
    }
    
    @Override
    public boolean canExecuteInTransaction() {
        return false; // Execute outside transaction for PostgreSQL DDL
    }
    
    @Override
    public void migrate(Context context) throws Exception {
        // Complex migration logic here
    }
}

Migration Context

Execution context providing access to database connection and configuration during migration execution.

/**
 * The context relevant to a migration.
 */
public interface Context {
    /** Get the configuration currently in use */
    Configuration getConfiguration();
    
    /** Get the JDBC connection to use to execute statements */
    Connection getConnection();
}

Usage Examples:

public class V4__Data_migration extends BaseJavaMigration {
    @Override
    public void migrate(Context context) throws Exception {
        Configuration config = context.getConfiguration();
        Connection connection = context.getConnection();
        
        // Use configuration
        String[] schemas = config.getSchemas();
        boolean createSchemas = config.isCreateSchemas();
        
        // Execute migration
        try (PreparedStatement stmt = connection.prepareStatement(
                "INSERT INTO users (name, created_date) VALUES (?, ?)")) {
            
            stmt.setString(1, "Migration User");
            stmt.setTimestamp(2, new Timestamp(System.currentTimeMillis()));
            stmt.executeUpdate();
        }
    }
}

Common Migration Patterns

Data Migration

public class V5__Migrate_user_data extends BaseJavaMigration {
    @Override
    public void migrate(Context context) throws Exception {
        Connection conn = context.getConnection();
        
        // Read from old table
        try (Statement select = conn.createStatement();
             ResultSet rs = select.executeQuery("SELECT * FROM old_users");
             PreparedStatement insert = conn.prepareStatement(
                 "INSERT INTO new_users (name, email, status) VALUES (?, ?, ?)")) {
            
            while (rs.next()) {
                insert.setString(1, rs.getString("username"));
                insert.setString(2, rs.getString("email_addr"));
                insert.setString(3, "ACTIVE");
                insert.addBatch();
            }
            insert.executeBatch();
        }
        
        // Drop old table
        try (Statement drop = conn.createStatement()) {
            drop.execute("DROP TABLE old_users");
        }
    }
}

Conditional Migration

public class V6__Conditional_index extends BaseJavaMigration {
    @Override
    public void migrate(Context context) throws Exception {
        Connection conn = context.getConnection();
        
        // Check if index already exists
        boolean indexExists = false;
        try (PreparedStatement stmt = conn.prepareStatement(
                "SELECT COUNT(*) FROM information_schema.statistics " +
                "WHERE table_name = ? AND index_name = ?")) {
            stmt.setString(1, "users");
            stmt.setString(2, "idx_user_email");
            
            try (ResultSet rs = stmt.executeQuery()) {
                if (rs.next() && rs.getInt(1) > 0) {
                    indexExists = true;
                }
            }
        }
        
        if (!indexExists) {
            try (Statement create = conn.createStatement()) {
                create.execute("CREATE INDEX idx_user_email ON users(email)");
            }
        }
    }
}

Configuration-Based Migration

public class V7__Environment_specific extends BaseJavaMigration {
    @Override
    public void migrate(Context context) throws Exception {
        Configuration config = context.getConfiguration();
        Connection conn = context.getConnection();
        
        // Get environment from system property or default
        String environment = System.getProperty("flyway.environment", "development");
        
        // Different behavior per environment
        String tableName = "development".equals(environment) ? "test_data" : "production_data";
        
        try (Statement stmt = conn.createStatement()) {
            stmt.execute("CREATE TABLE " + tableName + " (id SERIAL, data TEXT)");
            
            if ("development".equals(environment)) {
                // Insert test data in development
                stmt.execute("INSERT INTO " + tableName + " (data) VALUES ('test1'), ('test2')");
            }
        }
    }
}

Batch Processing Migration

public class V8__Batch_update extends BaseJavaMigration {
    private static final int BATCH_SIZE = 1000;
    
    @Override
    public void migrate(Context context) throws Exception {
        Connection conn = context.getConnection();
        
        try (PreparedStatement update = conn.prepareStatement(
                "UPDATE large_table SET processed = ? WHERE id = ?");
             Statement select = conn.createStatement();
             ResultSet rs = select.executeQuery("SELECT id FROM large_table WHERE processed IS NULL")) {
            
            int count = 0;
            while (rs.next()) {
                update.setBoolean(1, true);
                update.setLong(2, rs.getLong("id"));
                update.addBatch();
                
                if (++count % BATCH_SIZE == 0) {
                    update.executeBatch();
                    conn.commit(); // Commit in batches
                }
            }
            // Execute remaining batch
            if (count % BATCH_SIZE != 0) {
                update.executeBatch();
            }
        }
    }
}

Naming Conventions

Java migration classes must follow specific naming patterns for automatic version and description extraction:

  • Versioned migrations: V<version>__<description>.java

    • Example: V1_2_3__Create_user_table.java
    • Version: 1.2.3
    • Description: Create user table
  • Repeatable migrations: R__<description>.java

    • Example: R__Update_views.java
    • Description: Update views

Naming Examples:

// Version 1.0 - Initial setup
public class V1__Initial_setup extends BaseJavaMigration { }

// Version 2.1 - Add user table
public class V2_1__Add_user_table extends BaseJavaMigration { }

// Version 10.5.3 - Complex version
public class V10_5_3__Database_refactoring extends BaseJavaMigration { }

// Repeatable migration - runs on every migrate if changed
public class R__Update_calculated_fields extends BaseJavaMigration { }

Error Handling

public class V9__Error_handling extends BaseJavaMigration {
    @Override
    public void migrate(Context context) throws Exception {
        Connection conn = context.getConnection();
        
        try (Statement stmt = conn.createStatement()) {
            // Attempt migration
            stmt.execute("ALTER TABLE users ADD COLUMN new_field VARCHAR(50)");
            
        } catch (SQLException e) {
            // Check if column already exists
            if (e.getMessage().contains("duplicate column name") || 
                e.getMessage().contains("already exists")) {
                // Column exists, ignore error
                return;
            }
            // Re-throw other errors
            throw e;
        }
    }
}

Migration Lifecycle

  1. Discovery: Flyway scans classpath for classes implementing JavaMigration
  2. Version Extraction: Version and description extracted from class name
  3. Ordering: Migrations ordered by version number
  4. Execution: migrate() method called with execution context
  5. Recording: Successful migrations recorded in schema history table

Java migrations integrate seamlessly with SQL migrations, following the same versioning and execution order rules.

Install with Tessl CLI

npx tessl i tessl/maven-org-flywaydb--flyway-core

docs

callbacks.md

configuration.md

core-operations.md

error-handling.md

index.md

java-migrations.md

migration-info.md

tile.json