liveBook · Manning

Transactional annotation

As explained in Quarkus in Action

@Transactional is an annotation that defines a transaction boundary for an operation executed within a conventional transaction context.

A database write needs a transaction if the change is to be committed. In Quarkus, `@Transactional` marks a CDI method to run inside a transaction. The annotation comes from JTA (Java/Jakarta Transactions API). By default, a transaction starts before the method if one is not already active; when the method finishes, it is committed or rolled back depending on specified exceptions. section 7.2.2 section 7.5.1

Place `@Transactional` on a method that persists or changes entities. A reservation endpoint can call `reservation.persist()` inside an annotated `make` method. With a repository instead of an active-record entity, GraphQL mutations can annotate `register` and `remove` while calling repository `persist` and `delete`. Read methods in these examples are not annotated. section 7.1.3 section 7.2.2

Listing 7.17 The GraphQLInventoryService modified code
@GraphQLApi
public class GraphQLInventoryService {

    @Inject
    CarRepository carRepository;

    @Query
    public List<Car> cars() {
        return carRepository.listAll();
    }

    @Transactional
    @Mutation
    public Car register(Car car) {
        carRepository.persist(car);
        Log.info("Persisting " + car);
        return car;
    }

    @Transactional
    @Mutation
    public boolean remove(String licensePlateNumber) {
        Optional<Car> toBeRemoved = carRepository
            .findByLicensePlateNumberOptional(licensePlateNumber);
        if(toBeRemoved.isPresent()) {
            carRepository.delete(toBeRemoved.get());
            return true;
        } else {
            return false;
        }
    }

}

Compare the annotated write mutations with the unannotated `cars` query in the GraphQL inventory service. section 7.2.2

`Transactional.TxType` controls what happens when a method is called with or without an existing transaction:

  • `REQUIRED` (the default) joins an incoming transaction or starts one if none exists. section 7.5.1
  • `REQUIRES_NEW` starts a new transaction and suspends any existing one; `MANDATORY` requires an existing transaction or throws an exception. section 7.5.1
  • `SUPPORTS` joins an existing transaction but otherwise runs without one. `NOT_SUPPORTED` runs without one, suspending an incoming transaction. section 7.5.1
  • `NEVER` throws an exception if the method is called within a transaction. section 7.5.1

The annotation can also go on a class, applying to its methods unless an individual method overrides it. Choose the type for the operation: a reservation-statistics endpoint may not need a transaction, so `SUPPORTS` or `NOT_SUPPORTED` may fit better. Starting or suspending transactions adds invocation overhead. section 7.5.1

An annotation is not the only way to establish a boundary. When a gRPC inventory service persists cars inside a lambda outside the CDI bean invocation, it wraps each `persist(car)` call in `QuarkusTransaction.requiringNew().run(...)`. Each call then runs in its own transaction. section 7.2.2

How Quarkus in Action covers it

How deep it goes
Explains default boundary behavior, six transaction types, class-level use, and a programmatic alternative without detailing transaction-manager internals.
Examples
Uses a reservation POST that persists an entity, GraphQL mutations that persist or delete cars, and a gRPC lambda requiring a new transaction.
What you can do after
Apply it via Jakarta Transactions in Quarkus to bound CDI write methods and select transaction behavior for incoming calls.
How it connects
Connects Jakarta Transactions, CDI, Panache, the repository pattern, GraphQL mutations, and QuarkusTransaction.

Also explained in

Compare how 2 books explain Transactional annotation →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Quarkus in Action. The section links point to the parts of the book it draws on.