Integrating RabbitMQ for Delayed Order Cancellation in a Spring Boot Mall Application
This tutorial walks through installing RabbitMQ, configuring it in a Spring Boot project, defining message models and enums, implementing delayed messaging to automatically cancel unpaid orders, and provides complete Java code, configuration files, testing steps, and useful resources for building a robust backend order system.
Project Framework Introduction
RabbitMQ
RabbitMQ is a widely used open‑source message queue. It is lightweight, easy to deploy, supports multiple messaging protocols, and can be deployed in distributed and federated configurations to meet high‑scale, high‑availability requirements.
RabbitMQ Installation and Usage
1. Install Erlang. Download from http://erlang.org/download/otp (e.g., win64‑21.3.exe).
2. Install RabbitMQ. Download from https://dl.bintray.com/rabbitmq/all/rabbitmq-server/3.7.14/rabbitmq-server-3.7.14.exe .
3. After installation, go to the
sbindirectory under the RabbitMQ installation folder.
4. Open a command line in the folder and enable the management plugin:
<code>rabbitmq-plugins enable rabbitmq_management</code>5. Verify installation by visiting http://localhost:15672/ . Log in with username
guestand password
guest.
6. Create a user
mallwith password
malland assign the administrator role.
7. Create a new virtual host
/mall.
8. Click the
malluser to open the user configuration page and set permissions for the
/mallvirtual host.
9. At this point RabbitMQ installation and configuration are complete.
RabbitMQ Message Model
Producer (P) : Sends messages to an exchange.
Consumer (C) : Receives messages from a queue.
Exchange (X) : Receives messages from producers and routes them to queues based on routing keys.
Queue (Q) : Stores messages delivered by the exchange.
type : The exchange type, e.g.,
directroutes messages directly according to the routing key (e.g.,
orange/black).
Lombok
Lombok adds useful annotations to Java classes so you can avoid writing boilerplate getters, setters, etc. Install the Lombok plugin in IntelliJ IDEA and add the Lombok dependency in the project’s pom.xml .
Business Scenario Description
Cancel an order when the user’s order times out.
User places an order (locks inventory, applies coupons, points, etc.).
Generate the order and obtain the order ID.
Get the order timeout setting (e.g., 60 minutes).
Send a delayed message to RabbitMQ that will trigger order cancellation after the timeout.
If the user does not pay, cancel the order and release locked inventory, return coupons and points.
Integrating RabbitMQ for Delayed Messages
1. Add Dependencies in pom.xml
<code><!-- Message queue dependency -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!-- Lombok dependency -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency></code>2. Modify application.yml to Add RabbitMQ Settings
<code>rabbitmq:
host: localhost # RabbitMQ host
port: 5672 # RabbitMQ port
virtual-host: /mall # Virtual host
username: mall # Username
password: mall # Password
publisher-confirms: true # Enable publisher confirms for async messages</code>3. Define Message Queue Enum QueueEnum
<code>package com.macro.mall.tiny.dto;
import lombok.Getter;
/**
* Message queue enum configuration
*/
@Getter
public enum QueueEnum {
/** Message notification queue */
QUEUE_ORDER_CANCEL("mall.order.direct", "mall.order.cancel", "mall.order.cancel"),
/** Message notification TTL (delay) queue */
QUEUE_TTL_ORDER_CANCEL("mall.order.direct.ttl", "mall.order.cancel.ttl", "mall.order.cancel.ttl");
private String exchange;
private String name;
private String routeKey;
QueueEnum(String exchange, String name, String routeKey) {
this.exchange = exchange;
this.name = name;
this.routeKey = routeKey;
}
}</code>4. RabbitMQ Configuration RabbitMqConfig
<code>package com.macro.mall.tiny.config;
import com.macro.mall.tiny.dto.QueueEnum;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Message queue configuration
*/
@Configuration
public class RabbitMqConfig {
/** Order message actual consumption queue’s exchange */
@Bean
public DirectExchange orderDirect() {
return ExchangeBuilder.directExchange(QueueEnum.QUEUE_ORDER_CANCEL.getExchange())
.durable(true)
.build();
}
/** Order delay (TTL) queue’s exchange */
@Bean
public DirectExchange orderTtlDirect() {
return ExchangeBuilder.directExchange(QueueEnum.QUEUE_TTL_ORDER_CANCEL.getExchange())
.durable(true)
.build();
}
/** Order consumption queue */
@Bean
public Queue orderQueue() {
return new Queue(QueueEnum.QUEUE_ORDER_CANCEL.getName());
}
/** Order delay (TTL) queue – dead‑letter queue */
@Bean
public Queue orderTtlQueue() {
return QueueBuilder.durable(QueueEnum.QUEUE_TTL_ORDER_CANCEL.getName())
.withArgument("x-dead-letter-exchange", QueueEnum.QUEUE_ORDER_CANCEL.getExchange())
.withArgument("x-dead-letter-routing-key", QueueEnum.QUEUE_ORDER_CANCEL.getRouteKey())
.build();
}
/** Bind order queue to its exchange */
@Bean
public Binding orderBinding(DirectExchange orderDirect, Queue orderQueue) {
return BindingBuilder.bind(orderQueue)
.to(orderDirect)
.with(QueueEnum.QUEUE_ORDER_CANCEL.getRouteKey());
}
/** Bind order delay queue to its exchange */
@Bean
public Binding orderTtlBinding(DirectExchange orderTtlDirect, Queue orderTtlQueue) {
return BindingBuilder.bind(orderTtlQueue)
.to(orderTtlDirect)
.with(QueueEnum.QUEUE_TTL_ORDER_CANCEL.getRouteKey());
}
}
</code>5. Delayed Message Sender – CancelOrderSender
<code>package com.macro.mall.tiny.component;
import com.macro.mall.tiny.dto.QueueEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Sender for cancel‑order messages
*/
@Component
public class CancelOrderSender {
private static final Logger LOGGER = LoggerFactory.getLogger(CancelOrderSender.class);
@Autowired
private AmqpTemplate amqpTemplate;
public void sendMessage(Long orderId, final long delayTimes) {
// Send delayed message to the TTL queue
amqpTemplate.convertAndSend(
QueueEnum.QUEUE_TTL_ORDER_CANCEL.getExchange(),
QueueEnum.QUEUE_TTL_ORDER_CANCEL.getRouteKey(),
orderId,
new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
// Set expiration (delay) in milliseconds
message.getMessageProperties().setExpiration(String.valueOf(delayTimes));
return message;
}
});
LOGGER.info("send delay message orderId:{}", orderId);
}
}
</code>6. Cancel Order Receiver – CancelOrderReceiver
<code>package com.macro.mall.tiny.component;
import com.macro.mall.tiny.service.OmsPortalOrderService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Receiver for cancel‑order messages
*/
@Component
@RabbitListener(queues = "mall.order.cancel")
public class CancelOrderReceiver {
private static final Logger LOGGER = LoggerFactory.getLogger(CancelOrderReceiver.class);
@Autowired
private OmsPortalOrderService portalOrderService;
@RabbitHandler
public void handle(Long orderId) {
LOGGER.info("receive delay message orderId:{}", orderId);
portalOrderService.cancelOrder(orderId);
}
}
</code>7. Order Service Interface – OmsPortalOrderService
<code>package com.macro.mall.tiny.service;
import com.macro.mall.tiny.common.api.CommonResult;
import com.macro.mall.tiny.dto.OrderParam;
import org.springframework.transaction.annotation.Transactional;
/**
* Front‑end order management service
*/
public interface OmsPortalOrderService {
/**
* Generate an order based on submitted information
*/
@Transactional
CommonResult generateOrder(OrderParam orderParam);
/**
* Cancel a single timed‑out order
*/
@Transactional
void cancelOrder(Long orderId);
}
</code>8. Order Service Implementation – OmsPortalOrderServiceImpl
<code>package com.macro.mall.tiny.service.impl;
import com.macro.mall.tiny.common.api.CommonResult;
import com.macro.mall.tiny.component.CancelOrderSender;
import com.macro.mall.tiny.dto.OrderParam;
import com.macro.mall.tiny.service.OmsPortalOrderService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OmsPortalOrderServiceImpl implements OmsPortalOrderService {
private static final Logger LOGGER = LoggerFactory.getLogger(OmsPortalOrderServiceImpl.class);
@Autowired
private CancelOrderSender cancelOrderSender;
@Override
public CommonResult generateOrder(OrderParam orderParam) {
// TODO: implement order creation logic (refer to the original mall project)
LOGGER.info("process generateOrder");
// After order creation, send a delayed message to cancel the order if unpaid
sendDelayMessageCancelOrder(11L); // example orderId
return CommonResult.success(null, "Order placed successfully");
}
@Override
public void cancelOrder(Long orderId) {
// TODO: implement order cancellation logic (refer to the original mall project)
LOGGER.info("process cancelOrder orderId:{}", orderId);
}
/**
* Send a delayed message to cancel the order after the timeout period.
*/
private void sendDelayMessageCancelOrder(Long orderId) {
// Assume order timeout is 30 seconds for demonstration
long delayTimes = 30 * 1000L;
cancelOrderSender.sendMessage(orderId, delayTimes);
}
}
</code>9. Order Controller – OmsPortalOrderController
<code>package com.macro.mall.tiny.controller;
import com.macro.mall.tiny.dto.OrderParam;
import com.macro.mall.tiny.service.OmsPortalOrderService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* Order management controller
*/
@Controller
@Api(tags = "OmsPortalOrderController", description = "Order Management")
@RequestMapping("/order")
public class OmsPortalOrderController {
@Autowired
private OmsPortalOrderService portalOrderService;
@ApiOperation("Generate order based on shopping‑cart information")
@RequestMapping(value = "/generateOrder", method = RequestMethod.POST)
@ResponseBody
public Object generateOrder(@RequestBody OrderParam orderParam) {
return portalOrderService.generateOrder(orderParam);
}
}
</code>Interface Testing
Call the Order Generation Interface
Note: The delayed message time is set to 30 seconds.
Project Source Code
https://github.com/macrozheng/mall-learning/tree/master/mall-tiny-08
Recommended Reading
mall Architecture Overview
Learning Resources for mall
Integrating SpringBoot + MyBatis
Integrating Swagger‑UI for API Docs
Integrating Redis for Caching
SpringSecurity & JWT Authentication (Part 1)
SpringSecurity & JWT Authentication (Part 2)
SpringTask for Scheduled Jobs
Elasticsearch for Product Search
MongoDB for Document Operations
Follow us and give a like!
macrozheng
Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.