Learn Quarkus - Advanced Transactions & Persistence
Episode 10 of 24

Learn Quarkus - Advanced Transactions & Persistence

This episode covers transaction management with Narayana/JTA, propagation behavior and rollback, JPA optimization with fetch strategies and caching, as well as database migrations with Flyway or Liquibase.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

In episode 6 you stored data with Panache. But a production application needs more than simple CRUD: multi-table operations that must succeed or fail together, queries that aren't wasteful, and database schemas that evolve safely.

Episode 10 covers the advanced persistence layer: transaction management with Narayana/JTA, propagation behavior and rollback, JPA optimization with fetch strategies and caching, as well as database migrations with Flyway or Liquibase.

Transaction Management with Narayana/JTA

@Transactional

Quarkus uses Narayana as its JTA implementation. Transactions are started and ended automatically with the @Transactional annotation:

JavaTransaction with @Transactional
import jakarta.transaction.Transactional;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
 
@ApplicationScoped
public class OrderService {
 
    @Inject
    OrderRepository orderRepository;
 
    @Inject
    StockRepository stockRepository;
 
    @Transactional
    public void buatOrder(Order order) {
        orderRepository.persist(order);
        stockRepository.kurangiStok(order.itemId, order.qty);
    }
}

If kurangiStok throws an exception, the whole transaction is rolled back — the order isn't saved and the stock isn't reduced. @Transactional guarantees atomicity.

Rollback Points

A runtime exception triggers automatic rollback, while a checked exception does not. For checked exceptions, set rollbackOn:

JavaControlling rollback
import jakarta.transaction.Transactional;
 
@Transactional(rollbackOn = CustomBusinessException.class)
public void buatOrder() throws CustomBusinessException {
    // if CustomBusinessException is thrown, the transaction is rolled back
}

Or use dontRollbackOn to exclude certain exceptions from rollback.

Propagation Behavior and Rollback

Propagation

JTA in Quarkus supports propagation modes on @Transactional:

  • REQUIRED: join an existing transaction or create a new one. This is the default.
  • REQUIRES_NEW: always start a new transaction, pausing the outer one.
  • MANDATORY: a transaction must already exist, otherwise it errors.
  • NEVER: refuses if a transaction already exists.
  • SUPPORTS: joins a transaction if one exists, runs without one otherwise.

For example, @Transactional(Transactional.TxType.REQUIRES_NEW) is useful for writing audit logs that stay saved even if the main operation fails.

Manual Rollback

You can trigger a rollback explicitly with TransactionManager: in a catch block, call tm.setRollbackOnly() then rethrow the exception. The commit at the end of the transaction automatically becomes a rollback.

JPA Optimization, Fetch Strategy, and Caching

Fetch Strategy

JPA relationships have lazy and eager modes. The default for @OneToMany is lazy — child data is only loaded when accessed. To avoid N+1 queries, use a fetch join with PanacheQL:

JavaFetch join to avoid N+1
import io.quarkus.hibernate.orm.panache.PanacheRepository;
import jakarta.enterprise.context.ApplicationScoped;
 
@ApplicationScoped
public class OrderRepository implements PanacheRepository<Order> {
 
    public Order cariDenganItems(Long id) {
        return find("select o from Order o "
            + "left join fetch o.items where o.id = ?1", id)
            .firstResult();
    }
}

left join fetch o.items fetches the order along with its items in a single query — avoiding hundreds of small queries.

Second-Level Cache

Hibernate supports a second-level cache for entities that rarely change. Activate it with the following configuration:

Activate it with quarkus.hibernate-orm.cache.enabled=true. For very static data, cache at the application level with @CacheResult(cacheName = "produk") — repeated calls with the same id never touch the database.

Database Migrations with Flyway or Liquibase

Why Migrations Are Needed

drop-and-create is only safe in development. In production, schema changes must be controlled. Flyway and Liquibase manage database schema versions.

Flyway in Quarkus

Add it with ./mvnw quarkus:add-extension -Dextensions=flyway, then configure:

Flyway configuration
quarkus.flyway.migrate-at-start=true
quarkus.flyway.locations=db/migration

Create the migration file in src/main/resources/db/migration:

First migration V1
CREATE TABLE orders (
    id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    item_id BIGINT NOT NULL,
    qty INT NOT NULL
);

The file name follows the pattern V<n>__<description>.sql. When the application starts, Flyway runs the migrations that haven't been executed yet. The command ./mvnw quarkus:add-extension -Dextensions=flyway adds the extension.

Liquibase as an Alternative

If you choose Liquibase, its extension is:

If you choose Liquibase, add it with ./mvnw quarkus:add-extension -Dextensions=liquibase. Both are solid. Pick one and stay consistent. Important principle: migrations can only be added, never modified after they've been executed in an existing environment.

Wrap-Up

Episode 10 strengthens your application's data layer: understanding Narayana transactions with @Transactional, propagation and rollback control, JPA optimization with fetch strategies and the second-level cache, as well as safe database schema management with Flyway or Liquibase.

Key takeaways:

  • @Transactional makes operations atomic with automatic rollback on runtime exceptions.
  • rollbackOn and dontRollbackOn control exception behavior.
  • REQUIRES_NEW is suitable for audit logs that commit separately.
  • Fetch joins avoid the N+1 query problem.
  • The second-level cache speeds up access to data that rarely changes.
  • Flyway or Liquibase manages schema migrations in production.
  • Migration files can only be added, never modified after execution.

In episode 11 we'll cover batch jobs and scheduling — Quarkus Scheduler for scheduled tasks, building batch processing, task executor and concurrency control configuration, as well as job monitoring and retry handling.

Learn Quarkus - Advanced Transactions & Persistence | Learn Quarkus