advanced~4h

Spring Boot Integration — Flyway/Liquibase, Auditing & Multi-Database Setups

How a schema actually evolves safely over a real application's lifetime, plus two other everyday integration concerns: automatic auditing and running against more than one database at once.

Learning objectives

  • Explain why hibernate.hbm2ddl.auto=update is unsafe in production and what Flyway/Liquibase replace it with.
  • Configure @EnableJpaAuditing to automatically populate creation/modification timestamps.
  • Implement soft delete correctly, including consistent query filtering.

Every entity model in this course has assumed a database schema already exists, correctly — this chapter covers how that schema actually gets created and evolved safely over a real application's lifetime.

📖 Story

Imagine a house renovated over years — a room added, a wall moved — with no blueprint ever kept up to date, and no record of which change happened when. A new contractor has no reliable way to know the house's current structure. Here's the database equivalent, and why it's dangerous:

# DON'T do this in production: spring: jpa: hibernate: ddl-auto: update # Hibernate auto-generates/updates your schema

This is convenient in local development, but it can SILENTLY drop a column Hibernate thinks is no longer needed, with zero review process and zero awareness of your actual data. Here's the safe alternative:

-- V1__create_customers_table.sql (a Flyway migration) CREATE TABLE customers ( id BIGSERIAL PRIMARY KEY, name TEXT NOT NULL, email TEXT NOT NULL UNIQUE );
spring: jpa: hibernate: ddl-auto: validate # Hibernate only CHECKS the schema matches — never changes it flyway: enabled: true

Now every schema change is a reviewable, versioned, numbered SQL file — never a silent, automatic guess.

Flyway — a migration tool applying versioned SQL scripts in strict numbered order. Liquibase — a similar tool using XML/YAML changesets. @EnableJpaAuditing — enables automatic population of @CreatedDate/@LastModifiedDate fields. Soft delete — marking a row deleted rather than physically removing it.

Let's continue this chapter's customers table example with auditing and soft delete.

Auditing — automatic timestamps, zero manual code

@EntityListeners(AuditingEntityListener.class) @Entity public class Customer { @Id @GeneratedValue private Long id; @CreatedDate private Instant createdAt; @LastModifiedDate private Instant updatedAt; }
@Configuration @EnableJpaAuditing public class JpaConfig { }

With this in place, createdAt and updatedAt populate themselves automatically on every save/update — no explicit code needed anywhere in your service layer.

Soft delete — preserving history

@Entity public class Customer { @Id @GeneratedValue private Long id; private Instant deletedAt; // null = active public void softDelete() { this.deletedAt = Instant.now(); } } // Every "normal" query must remember to filter it out: @Query("SELECT c FROM Customer c WHERE c.deletedAt IS NULL") List<Customer> findAllActive();

Multi-database setup

An application reading from TWO genuinely separate databases needs two full sets of beans:

@Bean @Qualifier("primaryEntityManagerFactory") public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(...) { ... } @Bean @Qualifier("reportingEntityManagerFactory") public LocalContainerEntityManagerFactoryBean reportingEntityManagerFactory(...) { ... }

Flyway maintains a metadata table (flyway_schema_history) recording every migration applied, its checksum, and when — on startup, it compares this against the migration files present, applying any not-yet-recorded ones in strict numeric order, and failing loudly if an already-applied migration's checksum has changed. @EnableJpaAuditing's timestamp population is implemented via a Hibernate entity listener hooked into the @PrePersist/@PreUpdate JPA lifecycle callbacks from Chapter 5 — it runs automatically, requiring no explicit service-layer code.

  • Every real Spring Boot application in production uses Flyway or Liquibase as standard practice — ddl-auto: update in production is a red flag in almost any serious code review, exactly for the "silent column drop" risk this chapter's story describes.
  • A "trash/recycle bin" feature letting a user recover an accidentally deleted record is implemented via this chapter's soft-delete pattern.
  • Use Flyway or Liquibase in every shared/production environment — never rely on ddl-auto: update/create, exactly as this chapter's opening warning shows.
  • Keep migration files immutable once applied to any shared environment — add a new one for any further change.
  • Enable @EnableJpaAuditing as a standing default; it's essentially free.

⚠️ Why this keeps happening

ddl-auto: update feels genuinely convenient in early development — this exact convenience is what tempts teams to carry it into production, where its silent behavior becomes a real liability.

  • Using ddl-auto: update/create in production — this chapter's opening "DON'T do this" example.
  • Forgetting to filter soft-deleted rows in a NEW query, silently showing "deleted" records to users who shouldn't see them.
  • Editing an already-applied Flyway migration file instead of adding a new one — Flyway detects this via checksum mismatch and fails loudly.

A migration adding a NOT NULL column to a very large existing table can require a full table rewrite, holding locks — usually handled in steps (add nullable, backfill, then add the constraint) for large production tables. Add a partial index on the not-deleted subset (WHERE deleted_at IS NULL) to keep soft-delete's filter cheap.

@CreatedBy/@LastModifiedBy auditing is itself security-relevant — a reliable audit trail is often a compliance requirement (GDPR, SOC 2), and getting the AuditorAware implementation wrong undermines its entire value.

Track migration execution time and success/failure in your deployment pipeline explicitly — a migration that silently fails during a deploy is a serious production risk.

Always test migrations against a staging environment with production-shaped data before applying to production — this catches both performance surprises and correctness surprises before they become an incident.

  1. Write and apply this chapter's exact V1__create_customers_table.sql Flyway migration, then a second migration adding a column, and confirm Flyway tracks both correctly.
  2. Add @CreatedDate/@LastModifiedDate with @EnableJpaAuditing, save and update an entity, and confirm both timestamps populate with zero explicit code.
  3. Implement this chapter's soft-delete pattern and confirm a soft-deleted record is excluded from findAllActive() while still present in the table.

✓ Quick recap

  • Flyway/Liquibase provide versioned, reviewable schema migrations — never rely on ddl-auto: update in production, exactly this chapter's warning.
  • @EnableJpaAuditing automatically populates timestamps via a standard JPA lifecycle callback.
  • Soft delete preserves history at the cost of needing consistent query filtering everywhere.

Want a visual for this concept?

Generate a diagram tailored to “Spring Boot Integration — Flyway/Liquibase, Auditing & Multi-Database Setups” — the AI picks whichever visual (flowchart, comparison, sequence, etc.) best fits.

Sign in to generate a visual →

Practice quiz

Next Step

Continue to Microservices Persistence — Database-per-Service, Outbox Pattern & CDC← Back to all Spring Data JPA & Hibernate Mastery chapters