Most Liquibase guides explain change types by listing them. This article is different — it walks through twelve patterns you will encounter repeatedly in a real project, explains the MySQL-specific behaviour of each, and shows the rollback you need to write alongside every change. All examples build on the ecommerce database established in Part 1. By the end, you will have a complete reference you can reach for on any working day.
Continue reading »Tutorial
264 posts in this section
Configuring Jobs and Steps: Flows, Decisions, and Conditional Execution
Introduction So far every example has had a single step. Real batch jobs usually have multiple steps — validate input, import data, generate a report, send a notification, clean up temp files. Spring Batch’s JobBuilder DSL lets you compose these steps into flows with conditional branching, parallel execution, and decision logic. This article covers: Linear multi-step jobs Conditional step transitions using ExitStatus JobExecutionDecider for runtime branching Parallel flows with split Nested jobs with FlowStep The Flow abstraction for reusable step sequences Linear Multi-Step Job The simplest multi-step job runs steps in sequence.
Continue reading »Contexts and Labels: Multi-Environment Filtering
The most common multi-environment problem in database migrations: seed data that should run in dev and staging but must never touch production. The naive solution is maintaining separate changelogs per environment. The correct solution is contexts and labels — Liquibase’s built-in filtering mechanism that lets a single changelog serve every environment. This article covers both features, explains the critical difference between them, and shows exactly how to wire them into Spring Boot profiles.
Continue reading »Core Commands: update, rollback, status, history, validate, diff
Liquibase has over 50 commands. In practice, you will use fewer than ten of them for 95% of your work. This article covers those ten commands — what each one does, when to reach for it, and what the output tells you. Every example builds on the ecommerce database and users table from Article 4. The commands are organized by what you’re trying to accomplish: inspect, apply, undo, and verify.
Continue reading »Core Concepts: Changelog, Changeset, and Tracking Tables
Before writing a single migration, you need the mental model. Understanding how Liquibase thinks about changelogs, changesets, and identity prevents the most common mistakes — ones that are painful to fix after deployment. The Changelog The changelog is the file Liquibase reads. It contains an ordered list of changesets. Think of it as your database’s Git history — a sequential record of every change ever made. # db/changelog/db.changelog-master.yaml databaseChangeLog: - changeSet: id: "20240101-001" author: abhay changes: - createTable: tableName: users columns: - column: name: id type: BIGINT autoIncrement: true constraints: primaryKey: true nullable: false - changeSet: id: "20240101-002" author: abhay changes: - addColumn: tableName: users columns: - column: name: email type: VARCHAR(255) constraints: nullable: false unique: true The changelog format (YAML, XML, SQL) doesn’t matter for understanding the concept — it’s always a list of changesets in order.
Continue reading »CRUD Operations with JpaRepository
You have entities and repositories set up. Now let’s work through every data operation in depth — create, read, update, delete — and the JPA mechanics behind each. Create: save() @Service @RequiredArgsConstructor @Transactional public class OrderService { private final OrderRepository orderRepository; public Order createOrder(CreateOrderRequest request) { Order order = new Order(); order.setCustomerId(request.customerId()); order.setOrderNumber(generateOrderNumber()); order.setStatus(OrderStatus.PENDING); request.items().forEach(itemReq -> { OrderItem item = new OrderItem(); item.setProductId(itemReq.productId()); item.setQuantity(itemReq.quantity()); item.setUnitPrice(itemReq.unitPrice()); order.addItem(item); // manages bidirectional relationship }); return orderRepository.
Continue reading »Database Migrations with Flyway
Never use spring.jpa.hibernate.ddl-auto=update in production. It’s unpredictable, irreversible, and can corrupt data. Flyway gives you version-controlled, audited, reproducible schema changes. Why Flyway? Every database change runs as a versioned SQL script. Flyway tracks which scripts have run in a flyway_schema_history table. When the app starts: Flyway reads all migration files Checks which have already run (by checking the history table) Runs any new ones, in order If the current state doesn’t match the expected state → fails fast Benefits:
Continue reading »Diff, Snapshot, and Reverse Engineering: Onboarding Existing Databases
Most Liquibase tutorials start with a blank database. Most real projects start with a production database that has been evolving for years without version control. This article covers the complete workflow for adopting Liquibase on an existing database — generating a baseline changelog, bootstrapping Liquibase tracking, detecting drift between environments, and maintaining snapshots for offline comparisons. The Onboarding Problem An existing database has no DATABASECHANGELOG table. Running liquibase update with a fresh changelog would try to create tables that already exist, causing immediate failures.
Continue reading »Dockerizing Spring Boot Applications
Packaging your Spring Boot application as a Docker container is the standard way to deploy it — to Kubernetes, cloud platforms, or any container runtime. This article covers building production-quality images. The Naive Dockerfile (Don’t Use This) FROM eclipse-temurin:21-jdk COPY target/order-service.jar app.jar ENTRYPOINT ["java", "-jar", "app.jar"] Problems: 600MB+ image (JDK, not JRE) No layer caching — every code change rebuilds the whole JAR layer Runs as root (security risk) No health check Layered JARs (Better Cache Utilization) Spring Boot 3 creates layered JARs by default.
Continue reading »DTOs and Response Shaping
Every beginner makes the same mistake: returning JPA entities directly from REST controllers. This article explains why that’s dangerous, and how to design clean DTOs that make your API stable, secure, and maintainable. Why Not Return Entities Directly? Consider this: @GetMapping("/{id}") public Order getOrder(@PathVariable UUID id) { return orderRepository.findById(id).orElseThrow(); // Entity returned directly } Problems with this: 1. Serialization of lazy-loaded relationships crashes @Entity public class Order { @OneToMany(fetch = FetchType.
Continue reading »