0
# Java Migration Development
1
2
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.
3
4
## Capabilities
5
6
### JavaMigration Interface
7
8
Core contract for Java-based migrations providing version, description, and execution methods.
9
10
```java { .api }
11
/**
12
* Interface to be implemented by Java-based migrations.
13
*/
14
public interface JavaMigration {
15
/** Get the version of this migration */
16
MigrationVersion getVersion();
17
18
/** Get the description of this migration */
19
String getDescription();
20
21
/** Get the checksum of this migration. Can be null if no checksum is available */
22
Integer getChecksum();
23
24
/** Whether the execution should take place inside a transaction */
25
boolean canExecuteInTransaction();
26
27
/** Execute the migration */
28
void migrate(Context context) throws Exception;
29
}
30
```
31
32
### BaseJavaMigration Abstract Class
33
34
Base implementation providing default behavior and automatic version extraction from class names.
35
36
```java { .api }
37
/**
38
* Convenience class for Java-based migrations. Automatically extracts the version and description from the class name.
39
*/
40
public abstract class BaseJavaMigration implements JavaMigration {
41
/** Get the version from the migration class name */
42
public final MigrationVersion getVersion();
43
44
/** Get the description from the migration class name */
45
public String getDescription();
46
47
/** Get the checksum. Default implementation returns null */
48
public Integer getChecksum();
49
50
/** Whether this migration can execute within a transaction. Default is true */
51
public boolean canExecuteInTransaction();
52
53
/** Execute the migration - must be implemented by subclasses */
54
public abstract void migrate(Context context) throws Exception;
55
}
56
```
57
58
**Usage Examples:**
59
60
```java
61
// Version and description extracted from class name: V2_1__Create_user_table
62
public class V2_1__Create_user_table extends BaseJavaMigration {
63
@Override
64
public void migrate(Context context) throws Exception {
65
try (Statement statement = context.getConnection().createStatement()) {
66
statement.execute("CREATE TABLE users (id SERIAL PRIMARY KEY, name VARCHAR(100))");
67
}
68
}
69
}
70
71
// Custom checksum and transaction control
72
public class V3__Complex_migration extends BaseJavaMigration {
73
@Override
74
public Integer getChecksum() {
75
return 12345; // Custom checksum for this migration
76
}
77
78
@Override
79
public boolean canExecuteInTransaction() {
80
return false; // Execute outside transaction for PostgreSQL DDL
81
}
82
83
@Override
84
public void migrate(Context context) throws Exception {
85
// Complex migration logic here
86
}
87
}
88
```
89
90
### Migration Context
91
92
Execution context providing access to database connection and configuration during migration execution.
93
94
```java { .api }
95
/**
96
* The context relevant to a migration.
97
*/
98
public interface Context {
99
/** Get the configuration currently in use */
100
Configuration getConfiguration();
101
102
/** Get the JDBC connection to use to execute statements */
103
Connection getConnection();
104
}
105
```
106
107
**Usage Examples:**
108
109
```java
110
public class V4__Data_migration extends BaseJavaMigration {
111
@Override
112
public void migrate(Context context) throws Exception {
113
Configuration config = context.getConfiguration();
114
Connection connection = context.getConnection();
115
116
// Use configuration
117
String[] schemas = config.getSchemas();
118
boolean createSchemas = config.isCreateSchemas();
119
120
// Execute migration
121
try (PreparedStatement stmt = connection.prepareStatement(
122
"INSERT INTO users (name, created_date) VALUES (?, ?)")) {
123
124
stmt.setString(1, "Migration User");
125
stmt.setTimestamp(2, new Timestamp(System.currentTimeMillis()));
126
stmt.executeUpdate();
127
}
128
}
129
}
130
```
131
132
## Common Migration Patterns
133
134
### Data Migration
135
136
```java
137
public class V5__Migrate_user_data extends BaseJavaMigration {
138
@Override
139
public void migrate(Context context) throws Exception {
140
Connection conn = context.getConnection();
141
142
// Read from old table
143
try (Statement select = conn.createStatement();
144
ResultSet rs = select.executeQuery("SELECT * FROM old_users");
145
PreparedStatement insert = conn.prepareStatement(
146
"INSERT INTO new_users (name, email, status) VALUES (?, ?, ?)")) {
147
148
while (rs.next()) {
149
insert.setString(1, rs.getString("username"));
150
insert.setString(2, rs.getString("email_addr"));
151
insert.setString(3, "ACTIVE");
152
insert.addBatch();
153
}
154
insert.executeBatch();
155
}
156
157
// Drop old table
158
try (Statement drop = conn.createStatement()) {
159
drop.execute("DROP TABLE old_users");
160
}
161
}
162
}
163
```
164
165
### Conditional Migration
166
167
```java
168
public class V6__Conditional_index extends BaseJavaMigration {
169
@Override
170
public void migrate(Context context) throws Exception {
171
Connection conn = context.getConnection();
172
173
// Check if index already exists
174
boolean indexExists = false;
175
try (PreparedStatement stmt = conn.prepareStatement(
176
"SELECT COUNT(*) FROM information_schema.statistics " +
177
"WHERE table_name = ? AND index_name = ?")) {
178
stmt.setString(1, "users");
179
stmt.setString(2, "idx_user_email");
180
181
try (ResultSet rs = stmt.executeQuery()) {
182
if (rs.next() && rs.getInt(1) > 0) {
183
indexExists = true;
184
}
185
}
186
}
187
188
if (!indexExists) {
189
try (Statement create = conn.createStatement()) {
190
create.execute("CREATE INDEX idx_user_email ON users(email)");
191
}
192
}
193
}
194
}
195
```
196
197
### Configuration-Based Migration
198
199
```java
200
public class V7__Environment_specific extends BaseJavaMigration {
201
@Override
202
public void migrate(Context context) throws Exception {
203
Configuration config = context.getConfiguration();
204
Connection conn = context.getConnection();
205
206
// Get environment from system property or default
207
String environment = System.getProperty("flyway.environment", "development");
208
209
// Different behavior per environment
210
String tableName = "development".equals(environment) ? "test_data" : "production_data";
211
212
try (Statement stmt = conn.createStatement()) {
213
stmt.execute("CREATE TABLE " + tableName + " (id SERIAL, data TEXT)");
214
215
if ("development".equals(environment)) {
216
// Insert test data in development
217
stmt.execute("INSERT INTO " + tableName + " (data) VALUES ('test1'), ('test2')");
218
}
219
}
220
}
221
}
222
```
223
224
### Batch Processing Migration
225
226
```java
227
public class V8__Batch_update extends BaseJavaMigration {
228
private static final int BATCH_SIZE = 1000;
229
230
@Override
231
public void migrate(Context context) throws Exception {
232
Connection conn = context.getConnection();
233
234
try (PreparedStatement update = conn.prepareStatement(
235
"UPDATE large_table SET processed = ? WHERE id = ?");
236
Statement select = conn.createStatement();
237
ResultSet rs = select.executeQuery("SELECT id FROM large_table WHERE processed IS NULL")) {
238
239
int count = 0;
240
while (rs.next()) {
241
update.setBoolean(1, true);
242
update.setLong(2, rs.getLong("id"));
243
update.addBatch();
244
245
if (++count % BATCH_SIZE == 0) {
246
update.executeBatch();
247
conn.commit(); // Commit in batches
248
}
249
}
250
// Execute remaining batch
251
if (count % BATCH_SIZE != 0) {
252
update.executeBatch();
253
}
254
}
255
}
256
}
257
```
258
259
## Naming Conventions
260
261
Java migration classes must follow specific naming patterns for automatic version and description extraction:
262
263
- **Versioned migrations**: `V<version>__<description>.java`
264
- Example: `V1_2_3__Create_user_table.java`
265
- Version: `1.2.3`
266
- Description: `Create user table`
267
268
- **Repeatable migrations**: `R__<description>.java`
269
- Example: `R__Update_views.java`
270
- Description: `Update views`
271
272
**Naming Examples:**
273
274
```java
275
// Version 1.0 - Initial setup
276
public class V1__Initial_setup extends BaseJavaMigration { }
277
278
// Version 2.1 - Add user table
279
public class V2_1__Add_user_table extends BaseJavaMigration { }
280
281
// Version 10.5.3 - Complex version
282
public class V10_5_3__Database_refactoring extends BaseJavaMigration { }
283
284
// Repeatable migration - runs on every migrate if changed
285
public class R__Update_calculated_fields extends BaseJavaMigration { }
286
```
287
288
## Error Handling
289
290
```java
291
public class V9__Error_handling extends BaseJavaMigration {
292
@Override
293
public void migrate(Context context) throws Exception {
294
Connection conn = context.getConnection();
295
296
try (Statement stmt = conn.createStatement()) {
297
// Attempt migration
298
stmt.execute("ALTER TABLE users ADD COLUMN new_field VARCHAR(50)");
299
300
} catch (SQLException e) {
301
// Check if column already exists
302
if (e.getMessage().contains("duplicate column name") ||
303
e.getMessage().contains("already exists")) {
304
// Column exists, ignore error
305
return;
306
}
307
// Re-throw other errors
308
throw e;
309
}
310
}
311
}
312
```
313
314
## Migration Lifecycle
315
316
1. **Discovery**: Flyway scans classpath for classes implementing `JavaMigration`
317
2. **Version Extraction**: Version and description extracted from class name
318
3. **Ordering**: Migrations ordered by version number
319
4. **Execution**: `migrate()` method called with execution context
320
5. **Recording**: Successful migrations recorded in schema history table
321
322
Java migrations integrate seamlessly with SQL migrations, following the same versioning and execution order rules.