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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Wrapping a multi-step workflow in @Transactional makes any thrown exception roll back every prior write automatically.
- 2Constructor injection keeps collaborators final and makes the service's dependencies explicit and testable.
- 3Throwing domain exceptions mid-flow is the mechanism that aborts the whole transaction rather than leaving partial state.
Related explainers
python
from datetime import datetime from fastapi import APIRouter, Depends, HTTPException, status from pydantic import BaseModel, ConfigDict, EmailStr from sqlalchemy.orm import Session
Building a users router in FastAPI
routing
serialization
dependency-injection
Intermediate
7 steps
java
@Service public class OrderMetricsService { private final MeterRegistry registry;
Instrumenting orders with Micrometer in Spring
metrics
micrometer
observability
Intermediate
8 steps
typescript
import { Processor, WorkerHost, OnWorkerEvent } from '@nestjs/bullmq'; import { Logger } from '@nestjs/common'; import { Job } from 'bullmq'; import { MailerService } from '../mailer/mailer.service';
Processing background email jobs in NestJS
job-queue
background-processing
dependency-injection
Intermediate
7 steps
java
@RestController @RequestMapping("/api/files") public class FileUploadController {
Handling secure file uploads in Spring
file-upload
input-validation
path-traversal
Intermediate
10 steps
php
<?php declare(strict_types=1);
A single-action JSON user controller in PHP
invokable-class
json-api
validation
Intermediate
7 steps
rust
use std::sync::Arc; use axum::{ body::Body, extract::{Extension, State},
Passing per-request context through Axum middleware
middleware
request-context
dependency-injection
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/how-transactional-guards-a-spring-checkout-explained-java-a154/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.