Flyway (Postgres) ================= https://github.com/spring-boot-tutorials/spring-data-flyway-postgres In this article we will configure Flyway on Spring Boot. Based on: https://blog.jetbrains.com/idea/2024/11/how-to-use-flyway-for-database-migrations-in-spring-boot-applications/ Postgres Server --------------- Install and run server .. code-block:: sh docker run --name postgres-server \ -e POSTGRES_DB=testdb \ -e POSTGRES_USER=my_user \ -e POSTGRES_PASSWORD=my_password \ -p 5432:5432 \ postgres Create Initial Code Base ------------------------ - Go to https://start.spring.io/ - Add the following dependencies - jpa - validation - lombok - flyway - Click ``Generate`` Dependencies ------------ Dependencies used in ``pom.xml`` .. code-block:: xml org.springframework.boot spring-boot-starter-validation org.springframework.boot spring-boot-starter-data-jpa org.postgresql postgresql runtime org.flywaydb flyway-core org.flywaydb flyway-database-postgresql org.projectlombok lombok true Properties ---------- In ``src/main/resources/application.yaml`` let's add the following properties so the Spring Boot application can connect to the database .. code-block:: properties spring: datasource: url: jdbc:postgresql://localhost:5432/testdb username: my_user password: my_password driver-class-name: org.postgresql.Driver jpa: properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect Flyway Script #1 ---------------- Let's create a script ``src/main/resources/db/migration/V1__create_bookmarks_table.sql``: .. code-block:: sql CREATE SEQUENCE IF NOT EXISTS bookmark_id_seq START WITH 1 INCREMENT BY 50; CREATE TABLE bookmarks ( id BIGINT NOT NULL, title VARCHAR(200) NOT NULL, url VARCHAR(500) NOT NULL, created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL, updated_at TIMESTAMP WITHOUT TIME ZONE, CONSTRAINT pk_bookmarks PRIMARY KEY (id) ); Model ----- Let's create a new POJO ``src/main/java/com/example/Bookmark.java`` .. code-block:: java @Entity @Table(name = "bookmarks") @Data @SuperBuilder @NoArgsConstructor public class Bookmark { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "bookmarks_id_gen") @SequenceGenerator(name = "bookmarks_id_gen", sequenceName = "bookmark_id_seq") @Column(name = "id", nullable = false) private Long id; @Size(max = 200) @NotNull @Column(name = "title", nullable = false, length = 200) private String title; @Size(max = 500) @NotNull @Column(name = "url", nullable = false, length = 500) private String url; @NotNull @ColumnDefault("now()") @Column(name = "created_at", nullable = false) private Instant createdAt; @Column(name = "updated_at") private Instant updatedAt; } Repository ---------- Next we will create a Spring repository to CRUD against the database. This file will be called ``src/main/java/com/example/BookmarkRepository.java`` .. code-block:: java @Repository public interface BookmarkRepository extends CrudRepository { } Main ---- Now let's use this repository. Go back to ``MainApplication.java`` and add the following: .. code-block:: java @SpringBootApplication public class MainApplication implements CommandLineRunner { public static void main(String[] args) { SpringApplication.run(MainApplication.class, args); } @Autowired BookmarkRepository bookmarkRepository; @Override public void run(String... args) throws Exception { System.out.println("Count: " + bookmarkRepository.count()); Bookmark b = bookmarkRepository.save(Bookmark.builder() .title("title") .url("url") .createdAt(Instant.now()) .updatedAt(Instant.now()) .build()); System.out.println(b); System.out.println("Count: " + bookmarkRepository.count()); } } Run Application --------------- Open terminal at project root and execute the following: .. code-block:: sh mvn spring-boot:run There should be no errors and the output will display all the CRUD operations. Let's Simulate DB Schema Changes -------------------------------- Flyway Script #2 ---------------- Let's create another script ``src/main/resources/db/migration/V2__add_status_category_to_bookmarks.sql``: .. code-block:: sql CREATE SEQUENCE IF NOT EXISTS category_id_seq START WITH 1 INCREMENT BY 50; CREATE TABLE categories ( id BIGINT NOT NULL, name VARCHAR(255), CONSTRAINT pk_categories PRIMARY KEY (id) ); ALTER TABLE bookmarks ADD COLUMN status VARCHAR(255) DEFAULT 'DRAFT'; ALTER TABLE bookmarks ALTER COLUMN status SET NOT NULL; ALTER TABLE bookmarks ADD COLUMN category_id BIGINT; ALTER TABLE bookmarks ADD CONSTRAINT FK_ARTICLES_ON_CATEGORY FOREIGN KEY (category_id) REFERENCES categories (id); Model ----- Let's modify ``Bookmark.java``: .. code-block:: java public class Bookmark { // ... @Column(name = "updated_at") private Instant updatedAt; // START V2 Changes @NotNull @ColumnDefault("'DRAFT'") @Column(name = "status", nullable = false) private String status; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "category_id") private Category category; } Let's create another POJO ``Category.java``: .. code-block:: java @Entity @Data @SuperBuilder @NoArgsConstructor @Table(name = "categories") public class Category { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "category_id_generator") @SequenceGenerator(name = "category_id_generator", sequenceName = "category_id_seq") private Long id; private String name; } Main ---- Let's modify ``MainApplication.java`` and add ``status("DEFAULT")``: .. code-block:: java System.out.println("Count: " + bookmarkRepository.count()); Bookmark b = bookmarkRepository.save(Bookmark.builder() .title("title") .url("url") .createdAt(Instant.now()) .updatedAt(Instant.now()) .status("DEFAULT") .build()); System.out.println(b); System.out.println("Count: " + bookmarkRepository.count()); Run Application --------------- Open terminal at project root and execute the following: .. code-block:: sh mvn spring-boot:run There should be no errors and the output will display all the CRUD operations.