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
java
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = PasswordsMatchValidator.class) public @interface PasswordsMatch {
Custom cross-field validation in Spring
bean-validation
custom-constraints
cross-field-validation
Intermediate
8 steps
java
public final class LogRedactor { private static final Pattern SECRET = Pattern.compile( "(?i)(password|token|api[_-]?key|secret|authorization)\\s*[=:]\\s*\\S+");
Streaming log redaction in Java
regex
streaming-io
try-with-resources
Intermediate
9 steps
python
import secrets from fastapi import Depends, FastAPI, HTTPException, Security, status from fastapi.security import APIKeyHeader
API key authentication as a FastAPI dependency
authentication
dependency-injection
api-keys
Intermediate
8 steps
java
@Component public class HeaderMergeFilter { private static final String PER_REQUEST_HEADERS = HeaderMergeFilter.class.getName() + ".headers";
Merging default and per-request headers in Spring
webclient
filters
http-headers
Intermediate
8 steps
go
package middleware import ( "net/http"
Wrapping Gin requests in a DB transaction
middleware
transactions
error-handling
Intermediate
8 steps
java
import java.util.HashSet; import java.util.Set; public final class CollectionDiff<T> {
Diffing two collections with set operations
set-operations
immutability
generics
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.