or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

callbacks.mdconfiguration.mdcore-operations.mderror-handling.mdindex.mdjava-migrations.mdmigration-info.md

java-migrations.mddocs/

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.