Skip to content

Commit 0b1b815

Browse files
nanotaboadaclaude
andcommitted
feat(db): integrate Flyway for database migrations (#130)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent dc49f14 commit 0b1b815

8 files changed

Lines changed: 154 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,17 @@ Release names follow the **historic football clubs** naming convention (A–Z):
4242

4343
### Added
4444

45+
- Integrate Flyway for database schema versioning and automated migrations;
46+
add `flyway-core` and `flyway-database-postgresql` to `pom.xml`; create
47+
migration directory `src/main/resources/db/migration/` with three versioned
48+
scripts: `V1__Create_players_table.sql` (schema), `V2__Seed_starting11.sql`
49+
(11 Starting XI players), `V3__Seed_substitutes.sql` (15 substitute players);
50+
configure `spring.flyway.enabled=true` with `baseline-on-migrate=true` for
51+
backwards compatibility with existing databases; disable Flyway in test
52+
environment which continues to use SQLite in-memory with `ddl.sql`/`dml.sql`;
53+
switch `spring.jpa.hibernate.ddl-auto` from `none` to `validate` so Hibernate
54+
verifies entity mappings against the Flyway-managed schema (#130)
55+
4556
### Changed
4657

4758
- Switch runtime base image from `eclipse-temurin:25-jdk-alpine` to

README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,53 @@ spring.datasource.url=jdbc:sqlite::memory:
269269
spring.jpa.hibernate.ddl-auto=create-drop
270270
```
271271

272+
## Database Migrations
273+
274+
Schema versioning is managed by [Flyway](https://documentation.red-gate.com/flyway), which runs automatically on application startup and applies any pending migrations in order.
275+
276+
### Migration files
277+
278+
Versioned SQL scripts live in `src/main/resources/db/migration/` and follow the Flyway naming convention:
279+
280+
```
281+
V{version}__{description}.sql
282+
```
283+
284+
| File | Description |
285+
| ---- | ----------- |
286+
| `V1__Create_players_table.sql` | Creates the `players` table (schema) |
287+
| `V2__Seed_starting11.sql` | Seeds 11 Starting XI players (`starting11 = 1`) |
288+
| `V3__Seed_substitutes.sql` | Seeds 15 Substitute players (`starting11 = 0`) |
289+
290+
All migration SQL is written to be compatible with both **SQLite** (local dev) and **PostgreSQL** (see #286).
291+
292+
### Adding a new migration
293+
294+
Create a new file in `src/main/resources/db/migration/` with the next version number:
295+
296+
```bash
297+
touch src/main/resources/db/migration/V4__Add_nationality_column.sql
298+
```
299+
300+
Flyway applies it automatically on the next application startup. View the applied history by querying the `flyway_schema_history` table.
301+
302+
### Existing databases
303+
304+
`baseline-on-migrate=true` ensures that databases created before Flyway was introduced are recognised as already at `V3` (schema + full seed data), so no migrations run against them. Fresh databases (new file) run V1 → V2 → V3 from scratch.
305+
306+
### Reset local database
307+
308+
Delete the SQLite file and restart the application — Flyway recreates the schema and seed data automatically:
309+
310+
```bash
311+
rm storage/players-sqlite3.db
312+
./mvnw spring-boot:run
313+
```
314+
315+
### Tests
316+
317+
The test environment keeps `spring.flyway.enabled=false` and uses SQLite in-memory with `ddl.sql`/`dml.sql` via Spring SQL init for fast, isolated test execution.
318+
272319
## Contributing
273320

274321
Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on:

pom.xml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,29 @@
169169
<groupId>org.hibernate.orm</groupId>
170170
<artifactId>hibernate-community-dialects</artifactId>
171171
</dependency>
172+
<!-- Flyway Core ================================================== -->
173+
<!--
174+
Flyway is an open-source database migration tool. It provides version
175+
control for your database schema and automates migrations across all
176+
environments. Includes built-in support for SQLite (community).
177+
https://mvnrepository.com/artifact/org.flywaydb/flyway-core
178+
-->
179+
<dependency>
180+
<groupId>org.flywaydb</groupId>
181+
<artifactId>flyway-core</artifactId>
182+
</dependency>
183+
<!-- Flyway Database PostgreSQL ==================================== -->
184+
<!--
185+
Flyway 10+ modular architecture requires explicit database support
186+
modules for non-community databases. Required for PostgreSQL support
187+
(see #286 — Add PostgreSQL support with unified migration-based
188+
initialization).
189+
https://mvnrepository.com/artifact/org.flywaydb/flyway-database-postgresql
190+
-->
191+
<dependency>
192+
<groupId>org.flywaydb</groupId>
193+
<artifactId>flyway-database-postgresql</artifactId>
194+
</dependency>
172195
<!-- H2 Database Engine ============================================ -->
173196
<!--
174197
Provides a fast in-memory database for testing. Used only in test

src/main/resources/application.properties

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,18 @@ springdoc.swagger-ui.path=/swagger/index.html
2020
spring.datasource.url=jdbc:sqlite:${STORAGE_PATH:storage/players-sqlite3.db}
2121
spring.datasource.driver-class-name=org.sqlite.JDBC
2222
spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect
23-
spring.jpa.hibernate.ddl-auto=none
23+
spring.jpa.hibernate.ddl-auto=validate
2424
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
2525
spring.jpa.show-sql=false
2626
spring.jpa.properties.hibernate.format_sql=true
27+
28+
# Flyway Database Migration Configuration
29+
# Flyway manages all schema creation and seed data via versioned SQL migrations.
30+
# baseline-on-migrate: treats an existing database (without flyway_schema_history)
31+
# as already at baseline-version, avoiding re-running migrations on pre-seeded DBs.
32+
# baseline-version=3: baselines existing databases at V3 (schema + all seed data
33+
# already applied), while fresh databases run V1 → V2 → V3 from scratch.
34+
spring.flyway.enabled=true
35+
spring.flyway.locations=classpath:db/migration
36+
spring.flyway.baseline-on-migrate=true
37+
spring.flyway.baseline-version=3
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
-- V1: Create players table
2+
-- Compatible with both SQLite (local dev) and PostgreSQL (see #286).
3+
-- TEXT columns use TEXT affinity in SQLite and the unlimited TEXT type in PostgreSQL.
4+
-- INTEGER is used for squadNumber (natural key) and starting11 (boolean flag: 1/0).
5+
-- UUID primary key is stored as VARCHAR(36) and generated at the application level.
6+
7+
CREATE TABLE IF NOT EXISTS players (
8+
id VARCHAR(36) NOT NULL,
9+
squadNumber INTEGER NOT NULL,
10+
firstName TEXT NOT NULL,
11+
middleName TEXT,
12+
lastName TEXT NOT NULL,
13+
dateOfBirth TEXT NOT NULL,
14+
position TEXT NOT NULL,
15+
abbrPosition TEXT NOT NULL,
16+
team TEXT NOT NULL,
17+
league TEXT NOT NULL,
18+
starting11 INTEGER NOT NULL,
19+
PRIMARY KEY (id),
20+
UNIQUE (squadNumber)
21+
);
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
-- V2: Seed Starting XI players (starting11 = 1)
2+
-- Argentina 2022 FIFA World Cup squad — 11 players who started the final.
3+
-- Rolling back this migration removes only the Starting XI, leaving substitutes intact.
4+
5+
INSERT INTO players (id, squadNumber, firstName, middleName, lastName, dateOfBirth, position, abbrPosition, team, league, starting11) VALUES
6+
('01772c59-43f0-5d85-b913-c78e4e281452', 23, 'Damián', 'Emiliano', 'Martínez', '1992-09-02T00:00:00.000Z', 'Goalkeeper', 'GK', 'Aston Villa FC', 'Premier League', 1),
7+
('da31293b-4c7e-5e0f-a168-469ee29ecbc4', 26, 'Nahuel', NULL, 'Molina', '1998-04-06T00:00:00.000Z', 'Right-Back', 'RB', 'Atlético Madrid', 'La Liga', 1),
8+
('c096c69e-762b-5281-9290-bb9c167a24a0', 13, 'Cristian','Gabriel', 'Romero', '1998-04-27T00:00:00.000Z', 'Centre-Back', 'CB', 'Tottenham Hotspur', 'Premier League', 1),
9+
('d5f7dd7a-1dcb-5960-ba27-e34865b63358', 19, 'Nicolás', 'Hernán Gonzalo', 'Otamendi', '1988-02-12T00:00:00.000Z', 'Centre-Back', 'CB', 'SL Benfica', 'Liga Portugal', 1),
10+
('2f6f90a0-9b9d-5023-96d2-a2aaf03143a6', 3, 'Nicolás', 'Alejandro', 'Tagliafico','1992-08-31T00:00:00.000Z', 'Left-Back', 'LB', 'Olympique Lyon', 'Ligue 1', 1),
11+
('b5b46e79-929e-5ed2-949d-0d167109c022', 11, 'Ángel', 'Fabián', 'Di María', '1988-02-14T00:00:00.000Z', 'Right Winger', 'RW', 'SL Benfica', 'Liga Portugal', 1),
12+
('0293b282-1da8-562e-998e-83849b417a42', 7, 'Rodrigo', 'Javier', 'de Paul', '1994-05-24T00:00:00.000Z', 'Central Midfield','CM', 'Atlético Madrid', 'La Liga', 1),
13+
('d3ba552a-dac3-588a-b961-1ea7224017fd', 24, 'Enzo', 'Jeremías', 'Fernández', '2001-01-17T00:00:00.000Z', 'Central Midfield','CM', 'SL Benfica', 'Liga Portugal', 1),
14+
('9613cae9-16ab-5b54-937e-3135123b9e0d', 20, 'Alexis', NULL, 'Mac Allister','1998-12-24T00:00:00.000Z','Central Midfield','CM','Brighton & Hove Albion', 'Premier League', 1),
15+
('acc433bf-d505-51fe-831e-45eb44c4d43c', 10, 'Lionel', 'Andrés', 'Messi', '1987-06-24T00:00:00.000Z', 'Right Winger', 'RW', 'Paris Saint-Germain', 'Ligue 1', 1),
16+
('38bae91d-8519-55a2-b30a-b9fe38849bfb', 9, 'Julián', NULL, 'Álvarez', '2000-01-31T00:00:00.000Z', 'Centre-Forward', 'CF', 'Manchester City', 'Premier League', 1);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
-- V3: Seed Substitute players (starting11 = 0)
2+
-- Argentina 2022 FIFA World Cup squad — 15 players who did not start the final.
3+
-- Rolling back this migration removes only the substitutes, leaving the Starting XI intact.
4+
5+
INSERT INTO players (id, squadNumber, firstName, middleName, lastName, dateOfBirth, position, abbrPosition, team, league, starting11) VALUES
6+
('5a9cd988-95e6-54c1-bc34-9aa08acca8d0', 1, 'Franco', 'Daniel', 'Armani', '1986-10-16T00:00:00.000Z', 'Goalkeeper', 'GK', 'River Plate', 'Copa de la Liga', 0),
7+
('5fdb10e8-38c0-5084-9a3f-b369a960b9c2', 2, 'Juan', 'Marcos', 'Foyth', '1998-01-12T00:00:00.000Z', 'Right-Back', 'RB', 'Villarreal', 'La Liga', 0),
8+
('bbd441f7-fcfb-5834-8468-2a9004b64c8c', 4, 'Gonzalo', 'Ariel', 'Montiel', '1997-01-01T00:00:00.000Z', 'Right-Back', 'RB', 'Nottingham Forest', 'Premier League', 0),
9+
('9d140400-196f-55d8-86e1-e0b96a375c83', 5, 'Leandro', 'Daniel', 'Paredes', '1994-06-29T00:00:00.000Z', 'Defensive Midfield','DM', 'AS Roma', 'Serie A', 0),
10+
('d8bfea25-f189-5d5e-b3a5-ed89329b9f7c', 6, 'Germán', 'Alejo', 'Pezzella', '1991-06-27T00:00:00.000Z', 'Centre-Back', 'CB', 'Real Betis Balompié', 'La Liga', 0),
11+
('dca343a8-12e5-53d6-89a8-916b120a5ee4', 8, 'Marcos', 'Javier', 'Acuña', '1991-10-28T00:00:00.000Z', 'Left-Back', 'LB', 'Sevilla FC', 'La Liga', 0),
12+
('c62f2ac1-41e8-5d34-b073-2ba0913d0e31', 12, 'Gerónimo', NULL, 'Rulli', '1992-05-20T00:00:00.000Z', 'Goalkeeper', 'GK', 'Ajax Amsterdam', 'Eredivisie', 0),
13+
('d3b0e8e8-2c34-531a-b608-b24fed0ef986', 14, 'Exequiel', 'Alejandro', 'Palacios', '1998-10-05T00:00:00.000Z', 'Central Midfield', 'CM', 'Bayer 04 Leverkusen', 'Bundesliga', 0),
14+
('b1306b7b-a3a4-5f7c-90fd-dd5bdbed57ba', 15, 'Ángel', 'Martín', 'Correa', '1995-03-09T00:00:00.000Z', 'Right Winger', 'RW', 'Atlético Madrid', 'La Liga', 0),
15+
('ecec27e8-487b-5622-b116-0855020477ed', 16, 'Thiago', 'Ezequiel', 'Almada', '2001-04-26T00:00:00.000Z', 'Attacking Midfield','AM', 'Atlanta United FC', 'Major League Soccer',0),
16+
('7cc8d527-56a2-58bd-9528-2618fc139d30', 17, 'Alejandro', 'Darío', 'Gómez', '1988-02-15T00:00:00.000Z', 'Left Winger', 'LW', 'AC Monza', 'Serie A', 0),
17+
('191c82af-0c51-526a-b903-c3600b61b506', 18, 'Guido', NULL, 'Rodríguez', '1994-04-12T00:00:00.000Z', 'Defensive Midfield','DM', 'Real Betis Balompié', 'La Liga', 0),
18+
('7941cd7c-4df1-5952-97e8-1e7f5d08e8aa', 21, 'Paulo', 'Exequiel', 'Dybala', '1993-11-15T00:00:00.000Z', 'Second Striker', 'SS', 'AS Roma', 'Serie A', 0),
19+
('79c96f29-c59f-5f98-96b8-3a5946246624', 22, 'Lautaro', 'Javier', 'Martínez', '1997-08-22T00:00:00.000Z', 'Centre-Forward', 'CF', 'Inter Milan', 'Serie A', 0),
20+
('98306555-a466-5d18-804e-dc82175e697b', 25, 'Lisandro', NULL, 'Martínez', '1998-01-18T00:00:00.000Z', 'Centre-Back', 'CB', 'Manchester United', 'Premier League', 0);

src/test/resources/application.properties

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ spring.sql.init.data-locations=classpath:dml.sql
1515
spring.jpa.show-sql=false
1616
spring.jpa.properties.hibernate.format_sql=true
1717

18+
# Flyway Configuration (disabled for tests)
19+
# Tests use SQLite in-memory with Spring SQL init (ddl.sql + dml.sql) instead.
20+
spring.flyway.enabled=false
21+
1822
# Server Configuration (disabled for tests)
1923
server.port=0
2024
management.server.port=0

0 commit comments

Comments
 (0)