|
| 1 | +# Ebean ORM Bundle — DB Migrations (Flattened) |
| 2 | + |
| 3 | +> Flattened bundle. Content from source markdown guides is inlined below. |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +## Source: `add-ebean-db-migration-generation.md` |
| 8 | + |
| 9 | +# Guide: Add Ebean Database Migration Generation to an Existing Maven Project |
| 10 | + |
| 11 | +## Purpose |
| 12 | + |
| 13 | +This guide provides step-by-step instructions for adding Ebean DB migration generation |
| 14 | +to an existing Maven project that already uses Ebean ORM. Ebean generates migrations by |
| 15 | +performing a diff of the current entity model against the previously recorded model state, |
| 16 | +producing platform-specific DDL SQL scripts. |
| 17 | + |
| 18 | +These instructions are designed for AI agents and developers to follow precisely. |
| 19 | + |
| 20 | +--- |
| 21 | + |
| 22 | +## Prerequisites |
| 23 | + |
| 24 | +- An existing Maven project with Ebean ORM configured (entity beans present) |
| 25 | +- `ebean-test` is already a test-scoped dependency (from POM setup guide) |
| 26 | +- The project targets PostgreSQL (adjust `Platform.POSTGRES` for other databases) |
| 27 | + |
| 28 | +--- |
| 29 | + |
| 30 | +## Step 1 — Verify migration dependencies |
| 31 | + |
| 32 | +### Generation tooling (`ebean-ddl-generator`) |
| 33 | + |
| 34 | +`ebean-test` (already present as a test dependency) transitively includes |
| 35 | +`ebean-ddl-generator`, which provides the `DbMigration` class. No additional dependency |
| 36 | +is required for generation. |
| 37 | + |
| 38 | +### Runtime migration runner (`ebean-migration`) |
| 39 | + |
| 40 | +`ebean-migration` is the library that runs migrations on application startup. |
| 41 | +It is typically included **transitively** via `io.ebean:ebean-postgres` (or the |
| 42 | +equivalent platform dependency). Verify it is on the classpath by running: |
| 43 | + |
| 44 | +```bash |
| 45 | +mvn dependency:tree | grep ebean-migration |
| 46 | +``` |
| 47 | + |
| 48 | +If it is **not** present transitively, add it explicitly as a compile-scope dependency: |
| 49 | + |
| 50 | +```xml |
| 51 | +<dependency> |
| 52 | + <groupId>io.ebean</groupId> |
| 53 | + <artifactId>ebean-migration</artifactId> |
| 54 | + <version>${ebean.version}</version> |
| 55 | +</dependency> |
| 56 | +``` |
| 57 | + |
| 58 | +--- |
| 59 | + |
| 60 | +## Step 2 — Create `GenerateDbMigration.java` |
| 61 | + |
| 62 | +Create the following class in `src/test/java/main/`. This `main` method is run manually |
| 63 | +by a developer (or AI agent) whenever entity beans change and a new migration is needed. |
| 64 | + |
| 65 | +```java |
| 66 | +package main; |
| 67 | + |
| 68 | +import io.ebean.annotation.Platform; |
| 69 | +import io.ebean.dbmigration.DbMigration; |
| 70 | + |
| 71 | +import java.io.IOException; |
| 72 | + |
| 73 | +/** |
| 74 | + * Generate the next database migration based on a diff of the entity model. |
| 75 | + * Run this main method after making entity bean changes to produce the migration SQL. |
| 76 | + */ |
| 77 | +public class GenerateDbMigration { |
| 78 | + |
| 79 | + public static void main(String[] args) throws IOException { |
| 80 | + |
| 81 | + DbMigration migration = DbMigration.create(); |
| 82 | + migration.setPlatform(Platform.POSTGRES); |
| 83 | + |
| 84 | + migration.setVersion("1.1"); // set to the next migration version |
| 85 | + migration.setName("add-customer"); // short description of the change |
| 86 | + |
| 87 | + migration.generateMigration(); |
| 88 | + } |
| 89 | +} |
| 90 | +``` |
| 91 | + |
| 92 | +### Version naming convention |
| 93 | + |
| 94 | +Ebean supports two common version formats — choose one and apply it consistently: |
| 95 | + |
| 96 | +| Format | Example | Notes | |
| 97 | +|--------|---------|-------| |
| 98 | +| **Date-based** | `20240820` | `YYYYMMDD`; used when changes are tied to dates; easily sortable | |
| 99 | +| **Semantic** | `1.1`, `1.2`, `2.0` | Traditional versioning; useful for release-based workflows | |
| 100 | + |
| 101 | +The version controls execution order — Ebean runs migrations in ascending version order. |
| 102 | + |
| 103 | +### Name convention |
| 104 | + |
| 105 | +The `name` should be a short, lowercase, hyphenated description of the change: |
| 106 | +- `add-customer-email` |
| 107 | +- `rename-machine-type` |
| 108 | +- `drop-unused-columns` |
| 109 | + |
| 110 | +--- |
| 111 | + |
| 112 | +## Step 3 — Configure the output path (if needed) |
| 113 | + |
| 114 | +By default, migration files are written to `src/main/resources/dbmigration/` relative |
| 115 | +to the **current working directory** when `generateMigration()` is called. This is |
| 116 | +usually the module root, which is correct for single-module projects. |
| 117 | + |
| 118 | +For **multi-module projects** where `GenerateDbMigration` is in a submodule but the |
| 119 | +resources directory is at a different relative path, specify it explicitly: |
| 120 | + |
| 121 | +```java |
| 122 | +// Relative path from the working directory (project root) to the module's resources |
| 123 | +migration.setPathToResources("my-module/src/main/resources"); |
| 124 | +``` |
| 125 | + |
| 126 | +--- |
| 127 | + |
| 128 | +## Step 4 — Run `GenerateDbMigration` to produce the first migration |
| 129 | + |
| 130 | +Run the `main` method via the IDE or Maven: |
| 131 | + |
| 132 | +```bash |
| 133 | +# Run via Maven exec plugin (or use IDE run configuration) |
| 134 | +mvn test-compile exec:java \ |
| 135 | + -Dexec.mainClass="main.GenerateDbMigration" \ |
| 136 | + -Dexec.classpathScope="test" \ |
| 137 | + -pl <your-module> |
| 138 | +``` |
| 139 | + |
| 140 | +Ebean migration generation runs in **offline mode** — no database connection is required. |
| 141 | + |
| 142 | +### Expected output files |
| 143 | + |
| 144 | +After running, two files are created per migration in `src/main/resources/dbmigration/`: |
| 145 | + |
| 146 | +``` |
| 147 | +src/main/resources/dbmigration/ |
| 148 | + 1.1__add-customer.sql ← DDL SQL to apply (commit this) |
| 149 | + model/ |
| 150 | + 1.1__add-customer.model.xml ← logical model diff XML (commit this) |
| 151 | +``` |
| 152 | + |
| 153 | +Both files must be committed to source control. The `.model.xml` file records the |
| 154 | +logical state of the diff and is used by subsequent migration generations to determine |
| 155 | +what has changed. |
| 156 | + |
| 157 | +If **no entity beans have changed** since the last migration, the command outputs: |
| 158 | +``` |
| 159 | +DbMigration - no changes detected - no migration written |
| 160 | +``` |
| 161 | + |
| 162 | +--- |
| 163 | + |
| 164 | +## Step 5 — Enable the migration runner |
| 165 | + |
| 166 | +Configure Ebean to run pending migrations automatically on application startup. |
| 167 | + |
| 168 | +### Preferred approach — programmatic via `DatabaseBuilder` |
| 169 | + |
| 170 | +Set `runMigration(true)` directly on the `DatabaseBuilder` when constructing |
| 171 | +the `Database` bean. This is the preferred approach as it is explicit, co-located with |
| 172 | +the database configuration, and does not rely on external property files. |
| 173 | + |
| 174 | +In the `@Factory` class that builds the `Database` bean (see the database configuration |
| 175 | +guide), add `.runMigration(true)` to the builder chain: |
| 176 | + |
| 177 | +```java |
| 178 | +@Bean |
| 179 | +Database database(ConfigWrapper config) { |
| 180 | + var dataSource = DataSourceBuilder.create() |
| 181 | + .url(config.getDatabaseUrl()) |
| 182 | + .username(config.getDatabaseUser()) |
| 183 | + .password(config.getDatabasePassword()) |
| 184 | + // ... other datasource settings ... |
| 185 | + ; |
| 186 | + |
| 187 | + return Database.builder() |
| 188 | + .name("db") |
| 189 | + .dataSourceBuilder(dataSource) |
| 190 | + .runMigration(true) // run pending migrations on startup |
| 191 | + .build(); |
| 192 | +} |
| 193 | +``` |
| 194 | + |
| 195 | +If migrations should only run in certain environments (e.g., not in production, or |
| 196 | +only when a config flag is set), make it conditional: |
| 197 | + |
| 198 | +```java |
| 199 | +.runMigration(config.isRunMigrations()) // driven by config value |
| 200 | +``` |
| 201 | + |
| 202 | +### Alternative — via application properties |
| 203 | + |
| 204 | +If programmatic configuration is not available or not preferred, set the property |
| 205 | +in `src/main/resources/application.properties`: |
| 206 | + |
| 207 | +```properties |
| 208 | +ebean.migration.run=true |
| 209 | +``` |
| 210 | + |
| 211 | +Or in `src/main/resources/application.yaml`: |
| 212 | +```yaml |
| 213 | +ebean: |
| 214 | + migration: |
| 215 | + run: true |
| 216 | +``` |
| 217 | + |
| 218 | +For a **named database** (i.e., `Database.builder().name("mydb")`), use the database |
| 219 | +name in the property key: |
| 220 | + |
| 221 | +```properties |
| 222 | +ebean.mydb.migration.run=true |
| 223 | +``` |
| 224 | + |
| 225 | +### What the runner does at startup |
| 226 | + |
| 227 | +When migration running is enabled, Ebean will on each application start: |
| 228 | +1. Look at the migrations in `src/main/resources/dbmigration/` |
| 229 | +2. Compare against the `db_migration` table (created automatically on first run) |
| 230 | +3. Apply any migrations that have not yet been executed, in version order |
| 231 | +4. Record each successfully applied migration in `db_migration` |
| 232 | + |
| 233 | +--- |
| 234 | + |
| 235 | +## Step 6 — Commit the migration files |
| 236 | + |
| 237 | +Add both generated files to source control: |
| 238 | + |
| 239 | +```bash |
| 240 | +git add src/main/resources/dbmigration/1.1__add-customer.sql |
| 241 | +git add src/main/resources/dbmigration/model/1.1__add-customer.model.xml |
| 242 | +git commit -m "Add db migration 1.1: add-customer" |
| 243 | +``` |
| 244 | + |
| 245 | +--- |
| 246 | + |
| 247 | +## Ongoing workflow — generating subsequent migrations |
| 248 | + |
| 249 | +For each future set of entity bean changes: |
| 250 | + |
| 251 | +1. Make changes to the entity bean classes |
| 252 | +2. Update `GenerateDbMigration.java` with the **new version** and **new name**: |
| 253 | + ```java |
| 254 | + migration.setVersion("1.2"); |
| 255 | + migration.setName("add-address-table"); |
| 256 | + ``` |
| 257 | +3. Run the `main` method — a new `.sql` and `.model.xml` pair is written |
| 258 | +4. Review the generated `.sql` to confirm it reflects the intended changes |
| 259 | +5. Commit both files |
| 260 | + |
| 261 | +--- |
| 262 | + |
| 263 | +## Understanding the output files |
| 264 | + |
| 265 | +### Apply SQL (`.sql`) |
| 266 | + |
| 267 | +The apply SQL file contains the DDL that will be executed against the database: |
| 268 | + |
| 269 | +```sql |
| 270 | +-- apply changes |
| 271 | +alter table customer add column email varchar(255); |
| 272 | +``` |
| 273 | + |
| 274 | +### Model XML (`.model.xml`) |
| 275 | + |
| 276 | +The model XML records the logical diff in a database-agnostic format. Ebean uses |
| 277 | +this file on the next generation run to determine what has already been captured. |
| 278 | +It is not executed against the database. |
| 279 | + |
| 280 | +```xml |
| 281 | +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> |
| 282 | +<migration xmlns="http://ebean-orm.github.io/xml/ns/dbmigration"> |
| 283 | + <changeSet type="apply"> |
| 284 | + <addColumn tableName="customer"> |
| 285 | + <column name="email" type="varchar(255)"/> |
| 286 | + </addColumn> |
| 287 | + </changeSet> |
| 288 | +</migration> |
| 289 | +``` |
| 290 | + |
| 291 | +--- |
| 292 | + |
| 293 | +## Optional configurations |
| 294 | + |
| 295 | +### Multiple database platforms |
| 296 | + |
| 297 | +To generate migrations for multiple platforms simultaneously, use `addPlatform()` |
| 298 | +instead of `setPlatform()`: |
| 299 | + |
| 300 | +```java |
| 301 | +migration.addPlatform(Platform.POSTGRES); |
| 302 | +migration.addPlatform(Platform.SQLSERVER17); |
| 303 | +migration.addPlatform(Platform.MYSQL); |
| 304 | +``` |
| 305 | + |
| 306 | +Each platform gets its own subdirectory under `dbmigration/`. |
| 307 | + |
| 308 | +### Include index |
| 309 | + |
| 310 | +When enabled the migration generation also generates a file that contains |
| 311 | +all the migrations and their associated hashes. This is a performance |
| 312 | +optimisation (that will become the default) and means that the migration |
| 313 | +runner just needs to read the one resource and has the pre-computed hash |
| 314 | +values (so does not need to read each migration resource and compute the |
| 315 | +hash for each of those at runtime). |
| 316 | + |
| 317 | +```java |
| 318 | +migration.setIncludeIndex(true); |
| 319 | +``` |
| 320 | + |
| 321 | +### Strict mode |
| 322 | + |
| 323 | +Strict mode (on by default) errors if there are any pending drops not yet applied. |
| 324 | +Set to `false` to allow generation to proceed regardless: |
| 325 | + |
| 326 | +```java |
| 327 | +migration.setStrictMode(false); |
| 328 | +``` |
| 329 | + |
| 330 | +### Applying pending drops |
| 331 | + |
| 332 | +Destructive changes (drop column, drop table) are **not** included in the apply |
| 333 | +SQL by default — they are recorded as `pendingDrops` in the model XML. This allows |
| 334 | +the application to be deployed without immediately dropping columns (important for |
| 335 | +rolling deployments). |
| 336 | + |
| 337 | +The migration runner logs a message when pending drops exist: |
| 338 | +``` |
| 339 | +INFO DbMigration - Pending un-applied drops in versions [1.1] |
| 340 | +``` |
| 341 | + |
| 342 | +When ready to apply the drops, set `setGeneratePendingDrop` to the version that |
| 343 | +contains the pending drops: |
| 344 | + |
| 345 | +```java |
| 346 | +migration.setVersion("1.3"); |
| 347 | +migration.setName("drop-pending-from-1.1"); |
| 348 | +migration.setGeneratePendingDrop("1.1"); // apply drops recorded in version 1.1 |
| 349 | +migration.generateMigration(); |
| 350 | +``` |
| 351 | + |
| 352 | +### Custom dbSchema |
| 353 | + |
| 354 | +If the project uses a named Postgres schema (set via `ebean.dbSchema` in |
| 355 | +`application.properties`), no additional configuration is needed in |
| 356 | +`GenerateDbMigration` — Ebean picks up the schema from the application config |
| 357 | +automatically when running in offline mode. |
| 358 | + |
| 359 | +```properties |
| 360 | +# application.properties |
| 361 | +ebean.dbSchema=myschema |
| 362 | +``` |
| 363 | + |
| 364 | +--- |
| 365 | + |
| 366 | +## Troubleshooting |
| 367 | + |
| 368 | +| Symptom | Likely cause | Fix | |
| 369 | +|---------|-------------|-----| |
| 370 | +| `no changes detected - no migration written` | Entity beans unchanged since last migration | Make entity bean changes first, then re-run | |
| 371 | +| `DbMigration - Pending un-applied drops` | A previous migration has drops not yet applied | Either suppress with `setStrictMode(false)` or apply drops with `setGeneratePendingDrop(...)` | |
| 372 | +| Generated SQL is empty or wrong | Wrong working directory path | Set `setPathToResources(...)` to the correct module-relative path | |
| 373 | +| `ClassNotFoundException` for entity classes | Test classpath not including main classes | Ensure `exec.classpathScope=test` or run via IDE with test classpath | |
| 374 | +| Migrations not running on startup | Property key wrong or `ebean-migration` missing | Verify `ebean[.name].migration.run=true` and that `ebean-migration` is on the classpath | |
| 375 | + |
| 376 | + |
0 commit comments