java 41 lines · 6 steps

How @Transactional guards a Spring checkout

A Spring service coordinates inventory, payment, and loyalty in one atomic transaction that rolls back on any failure.

Explained by highlit
1@Service
2public class OrderCheckoutService {
3 
4 private final OrderRepository orderRepository;
5 private final InventoryRepository inventoryRepository;
6 private final PaymentGateway paymentGateway;
7 private final LoyaltyService loyaltyService;
8 
9 public OrderCheckoutService(OrderRepository orderRepository,
10 InventoryRepository inventoryRepository,
11 PaymentGateway paymentGateway,
12 LoyaltyService loyaltyService) {
13 this.orderRepository = orderRepository;
14 this.inventoryRepository = inventoryRepository;
15 this.paymentGateway = paymentGateway;
16 this.loyaltyService = loyaltyService;
17 }
18 
19 @Transactional
20 public Order checkout(CheckoutRequest request) {
21 Order order = orderRepository.save(Order.pending(request.customerId()));
22 
23 for (LineItem item : request.items()) {
24 int remaining = inventoryRepository.decrementStock(item.sku(), item.quantity());
25 if (remaining < 0) {
26 throw new InsufficientStockException(item.sku());
27 }
28 order.addLine(item);
29 }
30 
31 PaymentResult payment = paymentGateway.charge(request.customerId(), order.totalAmount());
32 if (!payment.approved()) {
33 throw new PaymentDeclinedException(payment.declineCode());
34 }
35 
36 order.markPaid(payment.transactionId());
37 loyaltyService.awardPoints(request.customerId(), order.totalAmount());
38 
39 return orderRepository.save(order);
40 }
41}
01 / 01
STEP 01

Walkthrough

Space play ←→ step click any line
Three takeaways
  1. 1Wrapping a multi-step workflow in @Transactional makes any thrown exception roll back every prior write automatically.
  2. 2Constructor injection keeps collaborators final and makes the service's dependencies explicit and testable.
  3. 3Throwing domain exceptions mid-flow is the mechanism that aborts the whole transaction rather than leaving partial state.

Related explainers

typescript
import { registerLocaleData } from '@angular/common';
import localeFr from '@angular/common/locales/fr';
import localeFrExtra from '@angular/common/locales/extra/fr';
import localeDe from '@angular/common/locales/de';

Locale-aware bootstrapping in Angular

i18n localization dependency-injection
Intermediate 8 steps
typescript
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import * as Joi from 'joi';
 

Validating env config at boot in NestJS

configuration schema-validation environment-variables
Intermediate 8 steps
php
<?php
 
namespace App\Services\Checkout;
 

Validating coupons with Laravel's Pipeline

pipeline chain of responsibility transactions
Intermediate 7 steps
java
@Component
@Converter
public class EncryptedStringConverter implements AttributeConverter<String, String> {
 

Transparent column encryption in Spring & JPA

encryption aes-gcm jpa-converter
Advanced 10 steps
typescript
import { Inject, Injectable, Logger } from '@nestjs/common';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
import { InjectRepository } from '@nestjs/typeorm';

A cache-aside country lookup in NestJS

cache-aside dependency-injection batch-lookup
Intermediate 8 steps
java
package com.acme.billing.config;
 
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;

Feature-flagged beans with Spring @ConditionalOnProperty

feature-flags conditional-beans strategy-pattern
Intermediate 5 steps

Share this explainer

Here's the card — post it anywhere.

How @Transactional guards a Spring checkout — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code