一、异常处理与可靠消息消费实践
SpringBoot集成ActiveMQ:异常处理与可靠消息消费实践(try-catch避免重试)
引言
在分布式系统中,消息队列(如ActiveMQ)常用于解耦生产者和消费者,提升系统的异步处理能力。但在实际应用中,消费者端的异常处理逻辑 往往容易被忽视,尤其是在要求 无论处理成功与否都必须确认消息并记录结果
的场景中。本文将基于SpringBoot和ActiveMQ,探讨如何在自动确认模式下,实现异常捕获、消息可靠消费与结果持久化的完整方案。
问题背景
- 消息必须被确认:无论消费者处理消息时是否抛出异常(包括业务异常和系统异常),消息都需被确认(从队列移除),避免触发重试机制。
- 记录处理结果:处理成功时记录成功状态,失败时记录失败原因(包括异常信息)。
- 避免手动确认的复杂性:尽量利用默认的自动确认模式,减少配置复杂度。
核心挑战
在默认的自动确认模式(AUTO_ACKNOWLEDGE
)下,若消费方法抛出异常,消息会因未被确认而重新入队(触发重试)。因此,必须确保:
- 异常不会传播到消息监听容器,否则容器会认为消息处理失败。
解决方案
1. 保持自动确认模式,但捕获所有异常
在消费方法中通过try-catch
捕获所有异常,确保方法正常退出,从而让容器认为消息已成功处理。
关键代码示例
@Component
public class MessageConsumer {
@Autowired
private TaskRecordService taskRecordService;
@JmsListener(destination = "your-queue")
public void consumeMessage(TextMessage message) {
String taskId = null;
try {
// 解析消息内容
String payload = message.getText();
Task task = parsePayload(payload);
taskId = task.getId();
// 处理业务逻辑(可能抛出异常)
processTask(task);
// 记录成功
taskRecordService.recordSuccess(taskId);
} catch (Exception e) {
// 记录失败(包括业务异常和其他异常)
if (taskId != null) {
taskRecordService.recordFailure(taskId, e.getMessage());
}
// 关键点:不抛出异常,确保方法正常退出
}
}
}
2. 数据库记录的可靠性保障
2.1 记录结果
@Service
public class TaskRecordService {
public void recordSuccess(String taskId) {
TaskRecord record = new TaskRecord(taskId, "SUCCESS", null);
taskRecordRepository.save(record);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void recordFailure(String taskId, String errorMessage) {
TaskRecord record = new TaskRecord(taskId, "FAILED", errorMessage);
taskRecordRepository.save(record);
}
}
2.2 防御数据库操作异常
在极端情况下(如数据库宕机),需捕获记录操作自身的异常,避免异常传递到消费方法外层。
@JmsListener(destination = "your-queue")
public void consumeMessage(TextMessage message) {
String taskId = null;
try {
// ... 业务处理逻辑 ...
taskRecordService.recordSuccess(taskId);
} catch (Exception e) {
try {
if (taskId != null) {
taskRecordService.recordFailure(taskId, e.getMessage());
}
} catch (Exception dbEx) {
// 记录日志,但不抛出异常
log.error("记录任务失败时发生异常: {}", dbEx.getMessage());
}
}
}
通过本文的方案,开发者可以在SpringBoot中快速实现ActiveMQ消费者的异常处理与可靠消费,平衡开发效率与系统可靠性。
二、示例项目及代码
项目结构示例:
TaskQueueConsumer
package com.example.hello.activemq.consumer;
import com.example.hello.activemq.model.TaskMessage;
import com.example.hello.activemq.service.TaskMessageParseService;
import com.example.hello.activemq.service.TaskRecordService;
import com.example.hello.activemq.service.TaskService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
/**
* 任务队列消费者
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class TaskQueueConsumer {
private final TaskService taskService;
private final TaskRecordService taskRecordService;
private final TaskMessageParseService taskMessageParseService;
/**
* 任务队列消费者
*/
@JmsListener(destination = "task-queue")
private void receive(String message) {
log.info("消息处理开始");
// 解析消息
log.info("消息内容(字符串): {}", message);
TaskMessage taskMessage = taskMessageParseService.parse(message);
log.info("解析消息内容为任务消息对象: {}", taskMessage);
if (taskMessage == null) {
log.info("消息处理结束:任务消息解析失败,提前返回");
return;
}
// 处理业务逻辑
processTask(taskMessage);
log.info("消息处理结束");
}
private void processTask(TaskMessage taskMessage) {
try {
// 处理业务逻辑(可能抛出异常)
taskService.processTask(taskMessage);
// 业务处理成功,记录任务执行明细(执行成功)到数据库
taskRecordService.recordSuccess(taskMessage);
} catch (Exception e) {
// 关键点:捕获异常后不抛出,确保方法正常退出
log.info("任务执行失败:", e);
try {
// 业务处理失败,记录任务执行明细(执行失败)到数据库
taskRecordService.recordFailure(taskMessage, e.getMessage());
} catch (Exception ex) {
log.error("记录任务失败时发生异常:", ex);
}
}
}
}
TaskService
package com.example.hello.activemq.service;
import com.example.hello.activemq.model.TaskMessage;
public interface TaskService {
void processTask(TaskMessage taskMessage);
}
TaskServiceImpl
package com.example.hello.activemq.service.impl;
import com.example.hello.activemq.model.TaskMessage;
import com.example.hello.activemq.service.TaskService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
@Slf4j
@Service
public class TaskServiceImpl implements TaskService {
/**
* 处理业务逻辑(可能抛出异常)
*/
@Override
public void processTask(TaskMessage taskMessage) {
// 模拟特定条件下抛出异常
LocalDate taskDate = taskMessage.getTaskDate();
LocalDate beginDate = LocalDate.of(2025, 1, 1);
if (taskDate != null && taskDate.isBefore(beginDate)) {
String message = String.format("【任务日期】小于【开始日期】,任务日期=%s,开始日期=%s", taskDate, beginDate);
throw new RuntimeException(message);
}
log.info("业务逻辑正常执行");
}
}
TaskRecordService
package com.example.hello.activemq.service;
import com.example.hello.activemq.model.TaskMessage;
/**
* 任务记录服务
* <br>
* 将任务执行结果记录到数据库中。
*/
public interface TaskRecordService {
void recordSuccess(TaskMessage taskMessage);
void recordFailure(TaskMessage taskMessage, String errorMessage);
void recordFailure(String rawMessage, String errorMessage);
}
TaskRecordServiceImpl
package com.example.hello.activemq.service.impl;
import com.example.hello.activemq.enumeration.TaskStatus;
import com.example.hello.activemq.mapper.TaskRecordMapper;
import com.example.hello.activemq.model.TaskMessage;
import com.example.hello.activemq.model.TaskRecord;
import com.example.hello.activemq.service.TaskRecordService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@Slf4j
@Service
@RequiredArgsConstructor
public class TaskRecordServiceImpl implements TaskRecordService {
private final TaskRecordMapper taskRecordMapper;
@Override
public void recordSuccess(TaskMessage taskMessage) {
TaskRecord taskRecord = getTaskRecord(taskMessage);
taskRecord.setStatus(TaskStatus.SUCCESS.getCode());
taskRecordMapper.insert(taskRecord);
log.info("记录任务执行明细(执行成功)到数据库。任务记录:{}", taskRecord);
}
@Override
public void recordFailure(TaskMessage taskMessage, String errorMessage) {
TaskRecord taskRecord = getTaskRecord(taskMessage);
taskRecord.setStatus(TaskStatus.FAILURE.getCode());
taskRecord.setErrorMessage(errorMessage);
taskRecordMapper.insert(taskRecord);
log.info("记录任务执行明细(执行失败)到数据库。任务记录:{}", taskRecord);
}
@Override
public void recordFailure(String rawMessage, String errorMessage) {
TaskRecord taskRecord = new TaskRecord();
// 使用时间作为主键,便于测试
taskRecord.setId(LocalDateTime.now().toString());
taskRecord.setStatus(TaskStatus.FAILURE.getCode());
taskRecord.setErrorMessage(errorMessage);
taskRecord.setTaskRawMessage(rawMessage);
taskRecord.setCreateTime(LocalDateTime.now());
taskRecordMapper.insert(taskRecord);
log.info("记录反序列化失败到数据库。任务记录:{}", taskRecord);
}
private TaskRecord getTaskRecord(TaskMessage taskMessage) {
TaskRecord taskRecord = new TaskRecord();
// 使用时间作为主键,便于测试
taskRecord.setId(LocalDateTime.now().toString());
taskRecord.setTaskId(taskMessage.getId());
taskRecord.setTaskCode(taskMessage.getTaskCode());
taskRecord.setTaskName(taskMessage.getTaskName());
taskRecord.setTaskDate(taskMessage.getTaskDate());
taskRecord.setAccount(taskMessage.getAccount());
taskRecord.setCreateTime(LocalDateTime.now());
return taskRecord;
}
}
TaskMessageParseService
package com.example.hello.activemq.service;
import com.example.hello.activemq.model.TaskMessage;
public interface TaskMessageParseService {
TaskMessage parse(String message);
}
TaskMessageParseServiceImpl
package com.example.hello.activemq.service.impl;
import com.example.hello.activemq.model.TaskMessage;
import com.example.hello.activemq.service.TaskMessageParseService;
import com.example.hello.activemq.service.TaskRecordService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* 任务消息解析服务(使用JSON反序列化)
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class TaskMessageParseServiceImpl implements TaskMessageParseService {
private final ObjectMapper objectMapper;
private final TaskRecordService taskRecordService;
/**
* 解析消息内容(JSON字符串)为任务消息对象
*/
@Override
public TaskMessage parse(String message) {
try {
return objectMapper.readValue(message, TaskMessage.class);
} catch (JsonProcessingException e) {
// 反序列化失败,记录原始消息到数据库
log.info("反序列化失败。原始消息:{}。异常详细信息(含堆栈):", message, e);
taskRecordService.recordFailure(message, e.getMessage());
return null;
}
}
}
TaskRecordMapper
package com.example.hello.activemq.mapper;
import com.example.hello.activemq.model.TaskRecord;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface TaskRecordMapper {
int insert(TaskRecord taskRecord);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.example.hello.activemq.mapper.TaskRecordMapper">
<insert id="insert" parameterType="com.example.hello.activemq.model.TaskRecord">
INSERT INTO task_record (
id,
status,
error_message,
task_id,
task_code,
task_name,
task_date,
account,
task_raw_message,
create_time
)
VALUES (
#{id},
#{status},
#{errorMessage},
#{taskId},
#{taskCode},
#{taskName},
#{taskDate},
#{account},
#{taskRawMessage},
#{createTime}
)
</insert>
</mapper>
实体类
TaskMessage
package com.example.hello.activemq.model;
import lombok.Data;
import java.time.LocalDate;
/**
* 任务消息
*/
@Data
public class TaskMessage {
/**
* 主键(雪花算法)
*/
private String id;
/**
* 任务编号
*/
private String taskCode;
/**
* 任务名称
*/
private String taskName;
/**
* 任务日期
*/
private LocalDate taskDate;
/**
* 账号
*/
private String account;
}
TaskRecord
package com.example.hello.activemq.model;
import lombok.Data;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* 任务记录
*/
@Data
public class TaskRecord {
/**
* 主键(雪花算法)
*/
private String id;
/**
* 状态:1=成功,0=失败
*/
private Integer status;
/**
* 错误信息:任务执行失败时记录错误信息
*/
private String errorMessage;
/**
* 任务ID(雪花算法)
*/
private String taskId;
/**
* 任务编号
*/
private String taskCode;
/**
* 任务名称
*/
private String taskName;
/**
* 任务日期
*/
private LocalDate taskDate;
/**
* 账号
*/
private String account;
/**
* 任务原始消息
*/
private String taskRawMessage;
/**
* 创建时间
*/
private LocalDateTime createTime;
}
枚举类
TaskStatus
package com.example.hello.activemq.enumeration;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 任务状态枚举
*/
@Getter
@AllArgsConstructor
public enum TaskStatus {
SUCCESS(1, "成功"),
FAILURE(0, "失败");
private final Integer code;
private final String description;
}
JSON:消息内容字符串
{
"id": "1234567890123456789",
"taskCode": "123456-20250101-20250101083000",
"taskName": "任务-123456-20250101-20250101083000",
"taskDate": "2025-01-01",
"account": "123456"
}
DDL:任务记录表
CREATE TABLE task_record (
id VARCHAR(64) NOT NULL COMMENT '主键(雪花算法)',
status TINYINT NOT NULL COMMENT '状态:1=成功,0=失败',
error_message TEXT COMMENT '错误信息',
task_id VARCHAR(64) COMMENT '任务ID(雪花算法)',
task_code VARCHAR(255) COMMENT '任务编号',
task_name VARCHAR(255) COMMENT '任务名称',
task_date DATE COMMENT '任务日期',
account VARCHAR(255) COMMENT '账号',
task_raw_message TEXT COMMENT '任务原始消息',
create_time DATETIME NOT NULL COMMENT '创建时间',
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='任务记录';
POM
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.4</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>hello-activemq</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>hello-activemq</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.4</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
项目启动状态
ActiveMQ启动成功
MySQL启动成功
应用启动成功
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.4.4)
2025-04-19T16:37:58.154+08:00 INFO 9584 --- [hello-activemq] [ main] c.e.h.activemq.HelloActivemqApplication : Starting HelloActivemqApplication using Java 21.0.1 with PID 9584 (E:\hello-world\hello-activemq\target\classes started by SongGuanxun in E:\hello-world\hello-activemq)
2025-04-19T16:37:58.170+08:00 INFO 9584 --- [hello-activemq] [ main] c.e.h.activemq.HelloActivemqApplication : No active profile set, falling back to 1 default profile: "default"
2025-04-19T16:38:03.830+08:00 INFO 9584 --- [hello-activemq] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http)
2025-04-19T16:38:03.902+08:00 INFO 9584 --- [hello-activemq] [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2025-04-19T16:38:03.904+08:00 INFO 9584 --- [hello-activemq] [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.39]
2025-04-19T16:38:04.093+08:00 INFO 9584 --- [hello-activemq] [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2025-04-19T16:38:04.098+08:00 INFO 9584 --- [hello-activemq] [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 5662 ms
2025-04-19T16:38:07.997+08:00 INFO 9584 --- [hello-activemq] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/'
2025-04-19T16:38:08.738+08:00 INFO 9584 --- [hello-activemq] [ main] c.e.h.activemq.HelloActivemqApplication : Started HelloActivemqApplication in 12.669 seconds (process running for 15.55)
三、场景测试与验证方案
测试一:业务执行成功
通过ActiveMQ的控制面板,发送符合任务执行成功条件的消息字符串。消费者在收到消息后,正常执行业务逻辑,然后将任务执行成功的状态记录到指定的任务记录表
中。
在整个接收消息、执行任务和记录任务成功状态的过程中,不会有报错。
发送消息
消息消费正常
日志打印
2025-04-19T21:41:47.509+08:00 INFO 3876 --- [hello-activemq] [ntContainer#1-1] c.e.h.a.consumer.TaskQueueConsumer : 消息处理开始
2025-04-19T21:41:47.535+08:00 INFO 3876 --- [hello-activemq] [ntContainer#1-1] c.e.h.a.consumer.TaskQueueConsumer : 消息内容(字符串): {
"id": "1234567890123456789",
"taskCode": "123456-20250101-20250101083000",
"taskName": "任务-123456-20250101-20250101083000",
"taskDate": "2025-01-01",
"account": "123456"
}
2025-04-19T21:41:48.070+08:00 INFO 3876 --- [hello-activemq] [ntContainer#1-1] c.e.h.a.consumer.TaskQueueConsumer : 解析消息内容为任务消息对象: TaskMessage(id=1234567890123456789, taskCode=123456-20250101-20250101083000, taskName=任务-123456-20250101-20250101083000, taskDate=2025-01-01, account=123456)
2025-04-19T21:41:48.079+08:00 INFO 3876 --- [hello-activemq] [ntContainer#1-1] c.e.h.a.service.impl.TaskServiceImpl : 业务逻辑正常执行
2025-04-19T21:41:48.269+08:00 INFO 3876 --- [hello-activemq] [ntContainer#1-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2025-04-19T21:41:50.545+08:00 INFO 3876 --- [hello-activemq] [ntContainer#1-1] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@64c25740
2025-04-19T21:41:50.566+08:00 INFO 3876 --- [hello-activemq] [ntContainer#1-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2025-04-19T21:41:50.893+08:00 INFO 3876 --- [hello-activemq] [ntContainer#1-1] c.e.h.a.s.impl.TaskRecordServiceImpl : 记录任务执行明细(执行成功)到数据库。任务记录:TaskRecord(id=2025-04-19T21:41:48.080351700, status=1, errorMessage=null, taskId=1234567890123456789, taskCode=123456-20250101-20250101083000, taskName=任务-123456-20250101-20250101083000, taskDate=2025-01-01, account=123456, taskRawMessage=null, createTime=2025-04-19T21:41:48.080351700)
2025-04-19T21:41:50.908+08:00 INFO 3876 --- [hello-activemq] [ntContainer#1-1] c.e.h.a.consumer.TaskQueueConsumer : 消息处理结束
数据库记录
测试二:业务执行失败
模拟业务执行失败,当不符合业务条件时,没有通过业务校验,导致抛出业务异常。此时,不会触发重试,消息会被 try-catch 捕获,然后将任务执行失败的信息记录到指定的 任务记录表
中。
发送消息
{
"id": "1234567890123456789",
"taskCode": "123456-20241231-20250101083000",
"taskName": "任务-123456-20241231-20250101083000",
"taskDate": "2024-12-31",
"account": "123456"
}
消息消费正常
日志打印
2025-04-19T23:51:51.229+08:00 INFO 3876 --- [hello-activemq] [ntContainer#1-1] c.e.h.a.consumer.TaskQueueConsumer : 消息处理开始
2025-04-19T23:51:51.231+08:00 INFO 3876 --- [hello-activemq] [ntContainer#1-1] c.e.h.a.consumer.TaskQueueConsumer : 消息内容(字符串): {
"id": "1234567890123456789",
"taskCode": "123456-20241231-20250101083000",
"taskName": "任务-123456-20241231-20250101083000",
"taskDate": "2024-12-31",
"account": "123456"
}
2025-04-19T23:51:51.248+08:00 INFO 3876 --- [hello-activemq] [ntContainer#1-1] c.e.h.a.consumer.TaskQueueConsumer : 解析消息内容为任务消息对象: TaskMessage(id=1234567890123456789, taskCode=123456-20241231-20250101083000, taskName=任务-123456-20241231-20250101083000, taskDate=2024-12-31, account=123456)
2025-04-19T23:51:51.253+08:00 INFO 3876 --- [hello-activemq] [ntContainer#1-1] c.e.h.a.consumer.TaskQueueConsumer : 任务执行失败:
java.lang.RuntimeException: 【任务日期】小于【开始日期】,任务日期=2024-12-31,开始日期=2025-01-01
at com.example.hello.activemq.service.impl.TaskServiceImpl.processTask(TaskServiceImpl.java:24) ~[classes/:na]
at com.example.hello.activemq.consumer.TaskQueueConsumer.processTask(TaskQueueConsumer.java:50) ~[classes/:na]
at com.example.hello.activemq.consumer.TaskQueueConsumer.receive(TaskQueueConsumer.java:42) ~[classes/:na]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:169) ~[spring-messaging-6.2.5.jar:6.2.5]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:119) ~[spring-messaging-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:110) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter.onMessage(MessagingMessageListenerAdapter.java:84) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:790) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:747) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:725) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.doReceiveAndExecute(AbstractPollingMessageListenerContainer.java:333) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.receiveAndExecute(AbstractPollingMessageListenerContainer.java:270) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.invokeListener(DefaultMessageListenerContainer.java:1420) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.executeOngoingLoop(DefaultMessageListenerContainer.java:1410) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.run(DefaultMessageListenerContainer.java:1287) ~[spring-jms-6.2.5.jar:6.2.5]
at java.base/java.lang.Thread.run(Thread.java:1583) ~[na:na]
2025-04-19T23:51:51.458+08:00 INFO 3876 --- [hello-activemq] [ntContainer#1-1] c.e.h.a.s.impl.TaskRecordServiceImpl : 记录任务执行明细(执行失败)到数据库。任务记录:TaskRecord(id=2025-04-19T23:51:51.370400600, status=0, errorMessage=【任务日期】小于【开始日期】,任务日期=2024-12-31,开始日期=2025-01-01, taskId=1234567890123456789, taskCode=123456-20241231-20250101083000, taskName=任务-123456-20241231-20250101083000, taskDate=2024-12-31, account=123456, taskRawMessage=null, createTime=2025-04-19T23:51:51.370400600)
2025-04-19T23:51:51.460+08:00 INFO 3876 --- [hello-activemq] [ntContainer#1-1] c.e.h.a.consumer.TaskQueueConsumer : 消息处理结束
数据库记录
测试三:JSON消息字符串解析异常
模拟在消费方法中,出现非业务执行失败,比如消息字符串无法正常解析为Java对象,导致抛出运行时异常。此时,不会触发重试,消息会被 try-catch 捕获,然后将任务执行失败的信息记录到指定的 任务记录表
中。
发送消息
消息消费正常
日志打印
2025-04-20T00:04:12.512+08:00 INFO 3876 --- [hello-activemq] [ntContainer#1-1] c.e.h.a.consumer.TaskQueueConsumer : 消息处理开始
2025-04-20T00:04:12.513+08:00 INFO 3876 --- [hello-activemq] [ntContainer#1-1] c.e.h.a.consumer.TaskQueueConsumer : 消息内容(字符串): Enter some text here for the message body...
2025-04-20T00:04:12.520+08:00 INFO 3876 --- [hello-activemq] [ntContainer#1-1] c.e.h.a.s.i.TaskMessageParseServiceImpl : 反序列化失败。原始消息:Enter some text here for the message body...。异常详细信息(含堆栈):
com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'Enter': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 6]
at com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:2584) ~[jackson-core-2.18.3.jar:2.18.3]
at com.fasterxml.jackson.core.JsonParser._constructReadException(JsonParser.java:2610) ~[jackson-core-2.18.3.jar:2.18.3]
at com.fasterxml.jackson.core.JsonParser._constructReadException(JsonParser.java:2618) ~[jackson-core-2.18.3.jar:2.18.3]
at com.fasterxml.jackson.core.base.ParserMinimalBase._reportError(ParserMinimalBase.java:825) ~[jackson-core-2.18.3.jar:2.18.3]
at com.fasterxml.jackson.core.json.ReaderBasedJsonParser._reportInvalidToken(ReaderBasedJsonParser.java:3017) ~[jackson-core-2.18.3.jar:2.18.3]
at com.fasterxml.jackson.core.json.ReaderBasedJsonParser._handleOddValue(ReaderBasedJsonParser.java:2051) ~[jackson-core-2.18.3.jar:2.18.3]
at com.fasterxml.jackson.core.json.ReaderBasedJsonParser.nextToken(ReaderBasedJsonParser.java:780) ~[jackson-core-2.18.3.jar:2.18.3]
at com.fasterxml.jackson.databind.ObjectMapper._initForReading(ObjectMapper.java:5018) ~[jackson-databind-2.18.3.jar:2.18.3]
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4921) ~[jackson-databind-2.18.3.jar:2.18.3]
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3868) ~[jackson-databind-2.18.3.jar:2.18.3]
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3836) ~[jackson-databind-2.18.3.jar:2.18.3]
at com.example.hello.activemq.service.impl.TaskMessageParseServiceImpl.parse(TaskMessageParseServiceImpl.java:28) ~[classes/:na]
at com.example.hello.activemq.consumer.TaskQueueConsumer.receive(TaskQueueConsumer.java:33) ~[classes/:na]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:169) ~[spring-messaging-6.2.5.jar:6.2.5]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:119) ~[spring-messaging-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:110) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter.onMessage(MessagingMessageListenerAdapter.java:84) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:790) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:747) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:725) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.doReceiveAndExecute(AbstractPollingMessageListenerContainer.java:333) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.receiveAndExecute(AbstractPollingMessageListenerContainer.java:270) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.invokeListener(DefaultMessageListenerContainer.java:1420) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.executeOngoingLoop(DefaultMessageListenerContainer.java:1410) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.run(DefaultMessageListenerContainer.java:1287) ~[spring-jms-6.2.5.jar:6.2.5]
at java.base/java.lang.Thread.run(Thread.java:1583) ~[na:na]
2025-04-20T00:04:12.567+08:00 INFO 3876 --- [hello-activemq] [ntContainer#1-1] c.e.h.a.s.impl.TaskRecordServiceImpl : 记录反序列化失败到数据库。任务记录:TaskRecord(id=2025-04-20T00:04:12.537009, status=0, errorMessage=Unrecognized token 'Enter': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 6], taskId=null, taskCode=null, taskName=null, taskDate=null, account=null, taskRawMessage=Enter some text here for the message body..., createTime=2025-04-20T00:04:12.537995400)
2025-04-20T00:04:12.568+08:00 INFO 3876 --- [hello-activemq] [ntContainer#1-1] c.e.h.a.consumer.TaskQueueConsumer : 解析消息内容为任务消息对象: null
2025-04-20T00:04:12.569+08:00 INFO 3876 --- [hello-activemq] [ntContainer#1-1] c.e.h.a.consumer.TaskQueueConsumer : 消息处理结束:任务消息解析失败,提前返回
数据库记录
测试四:记录任务数据库写入异常
模拟在消费方法中,出现数据库异常(比如,数据库连接异常),导致数据库写入失败,不会触发重试。消息会被 try-catch 捕获,打印对应日志。
注意,记录任务执行明细(执行失败)到数据库
这段代码必须要使用 try-catch
处理。否则,一旦出现数据库异常时,写入数据库失败,会将异常向外抛出到 消息监听容器
,导致触发重试。
try {
// 业务处理失败,记录任务执行明细(执行失败)到数据库
taskRecordService.recordFailure(taskMessage, e.getMessage());
} catch (Exception ex) {
log.error("记录任务失败时发生异常:", ex);
}
模拟数据库宕机
模拟数据库宕机,关闭MySQL数据库。
发送消息
消息消费正常
日志打印
2025-04-20T00:25:02.943+08:00 INFO 3876 --- [hello-activemq] [ntContainer#1-1] c.e.h.a.consumer.TaskQueueConsumer : 消息处理开始
2025-04-20T00:25:02.944+08:00 INFO 3876 --- [hello-activemq] [ntContainer#1-1] c.e.h.a.consumer.TaskQueueConsumer : 消息内容(字符串): {
"id": "1234567890123456789",
"taskCode": "123456-20250101-20250101083000",
"taskName": "任务-123456-20250101-20250101083000",
"taskDate": "2025-01-01",
"account": "123456"
}
2025-04-20T00:25:02.946+08:00 INFO 3876 --- [hello-activemq] [ntContainer#1-1] c.e.h.a.consumer.TaskQueueConsumer : 解析消息内容为任务消息对象: TaskMessage(id=1234567890123456789, taskCode=123456-20250101-20250101083000, taskName=任务-123456-20250101-20250101083000, taskDate=2025-01-01, account=123456)
2025-04-20T00:25:02.946+08:00 INFO 3876 --- [hello-activemq] [ntContainer#1-1] c.e.h.a.service.impl.TaskServiceImpl : 业务逻辑正常执行
2025-04-20T00:25:03.244+08:00 WARN 3876 --- [hello-activemq] [ntContainer#1-1] com.zaxxer.hikari.pool.PoolBase : HikariPool-1 - Failed to validate connection com.mysql.cj.jdbc.ConnectionImpl@73c43b25 (No operations allowed after connection closed.). Possibly consider using a shorter maxLifetime value.
2025-04-20T00:25:03.257+08:00 WARN 3876 --- [hello-activemq] [ntContainer#1-1] com.zaxxer.hikari.pool.PoolBase : HikariPool-1 - Failed to validate connection com.mysql.cj.jdbc.ConnectionImpl@52ec7b7 (No operations allowed after connection closed.). Possibly consider using a shorter maxLifetime value.
2025-04-20T00:25:03.273+08:00 WARN 3876 --- [hello-activemq] [ntContainer#1-1] com.zaxxer.hikari.pool.PoolBase : HikariPool-1 - Failed to validate connection com.mysql.cj.jdbc.ConnectionImpl@15222032 (No operations allowed after connection closed.). Possibly consider using a shorter maxLifetime value.
2025-04-20T00:25:03.285+08:00 WARN 3876 --- [hello-activemq] [ntContainer#1-1] com.zaxxer.hikari.pool.PoolBase : HikariPool-1 - Failed to validate connection com.mysql.cj.jdbc.ConnectionImpl@47ed2ca0 (No operations allowed after connection closed.). Possibly consider using a shorter maxLifetime value.
2025-04-20T00:25:03.292+08:00 WARN 3876 --- [hello-activemq] [ntContainer#1-1] com.zaxxer.hikari.pool.PoolBase : HikariPool-1 - Failed to validate connection com.mysql.cj.jdbc.ConnectionImpl@2afec909 (No operations allowed after connection closed.). Possibly consider using a shorter maxLifetime value.
2025-04-20T00:25:03.300+08:00 WARN 3876 --- [hello-activemq] [ntContainer#1-1] com.zaxxer.hikari.pool.PoolBase : HikariPool-1 - Failed to validate connection com.mysql.cj.jdbc.ConnectionImpl@599eb09e (No operations allowed after connection closed.). Possibly consider using a shorter maxLifetime value.
2025-04-20T00:25:03.307+08:00 WARN 3876 --- [hello-activemq] [ntContainer#1-1] com.zaxxer.hikari.pool.PoolBase : HikariPool-1 - Failed to validate connection com.mysql.cj.jdbc.ConnectionImpl@5db9382f (No operations allowed after connection closed.). Possibly consider using a shorter maxLifetime value.
2025-04-20T00:25:03.318+08:00 WARN 3876 --- [hello-activemq] [ntContainer#1-1] com.zaxxer.hikari.pool.PoolBase : HikariPool-1 - Failed to validate connection com.mysql.cj.jdbc.ConnectionImpl@739f8714 (No operations allowed after connection closed.). Possibly consider using a shorter maxLifetime value.
2025-04-20T00:25:03.322+08:00 WARN 3876 --- [hello-activemq] [ntContainer#1-1] com.zaxxer.hikari.pool.PoolBase : HikariPool-1 - Failed to validate connection com.mysql.cj.jdbc.ConnectionImpl@2f75beef (No operations allowed after connection closed.). Possibly consider using a shorter maxLifetime value.
2025-04-20T00:25:03.331+08:00 WARN 3876 --- [hello-activemq] [ntContainer#1-1] com.zaxxer.hikari.pool.PoolBase : HikariPool-1 - Failed to validate connection com.mysql.cj.jdbc.ConnectionImpl@343e1df (No operations allowed after connection closed.). Possibly consider using a shorter maxLifetime value.
2025-04-20T00:25:33.023+08:00 INFO 3876 --- [hello-activemq] [ntContainer#1-1] c.e.h.a.consumer.TaskQueueConsumer : 任务执行失败:
org.mybatis.spring.MyBatisSystemException:
### Error updating database. Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection
### The error may exist in file [E:\hello-world\hello-activemq\target\classes\mapper\TaskRecordMapper.xml]
### The error may involve com.example.hello.activemq.mapper.TaskRecordMapper.insert
### The error occurred while executing an update
### Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection
at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:99) ~[mybatis-spring-3.0.4.jar:3.0.4]
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:347) ~[mybatis-spring-3.0.4.jar:3.0.4]
at jdk.proxy2/jdk.proxy2.$Proxy61.insert(Unknown Source) ~[na:na]
at org.mybatis.spring.SqlSessionTemplate.insert(SqlSessionTemplate.java:224) ~[mybatis-spring-3.0.4.jar:3.0.4]
at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:62) ~[mybatis-3.5.17.jar:3.5.17]
at org.apache.ibatis.binding.MapperProxy$PlainMethodInvoker.invoke(MapperProxy.java:141) ~[mybatis-3.5.17.jar:3.5.17]
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:86) ~[mybatis-3.5.17.jar:3.5.17]
at jdk.proxy2/jdk.proxy2.$Proxy62.insert(Unknown Source) ~[na:na]
at com.example.hello.activemq.service.impl.TaskRecordServiceImpl.recordSuccess(TaskRecordServiceImpl.java:25) ~[classes/:na]
at com.example.hello.activemq.consumer.TaskQueueConsumer.processTask(TaskQueueConsumer.java:53) ~[classes/:na]
at com.example.hello.activemq.consumer.TaskQueueConsumer.receive(TaskQueueConsumer.java:42) ~[classes/:na]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:169) ~[spring-messaging-6.2.5.jar:6.2.5]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:119) ~[spring-messaging-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:110) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter.onMessage(MessagingMessageListenerAdapter.java:84) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:790) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:747) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:725) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.doReceiveAndExecute(AbstractPollingMessageListenerContainer.java:333) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.receiveAndExecute(AbstractPollingMessageListenerContainer.java:270) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.invokeListener(DefaultMessageListenerContainer.java:1420) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.executeOngoingLoop(DefaultMessageListenerContainer.java:1410) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.run(DefaultMessageListenerContainer.java:1287) ~[spring-jms-6.2.5.jar:6.2.5]
at java.base/java.lang.Thread.run(Thread.java:1583) ~[na:na]
Caused by: org.apache.ibatis.exceptions.PersistenceException:
### Error updating database. Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection
### The error may exist in file [E:\hello-world\hello-activemq\target\classes\mapper\TaskRecordMapper.xml]
### The error may involve com.example.hello.activemq.mapper.TaskRecordMapper.insert
### The error occurred while executing an update
### Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection
at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30) ~[mybatis-3.5.17.jar:3.5.17]
at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:199) ~[mybatis-3.5.17.jar:3.5.17]
at org.apache.ibatis.session.defaults.DefaultSqlSession.insert(DefaultSqlSession.java:184) ~[mybatis-3.5.17.jar:3.5.17]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:333) ~[mybatis-spring-3.0.4.jar:3.0.4]
... 24 common frames omitted
Caused by: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection
at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:84) ~[spring-jdbc-6.2.5.jar:6.2.5]
at org.mybatis.spring.transaction.SpringManagedTransaction.openConnection(SpringManagedTransaction.java:77) ~[mybatis-spring-3.0.4.jar:3.0.4]
at org.mybatis.spring.transaction.SpringManagedTransaction.getConnection(SpringManagedTransaction.java:64) ~[mybatis-spring-3.0.4.jar:3.0.4]
at org.apache.ibatis.executor.BaseExecutor.getConnection(BaseExecutor.java:348) ~[mybatis-3.5.17.jar:3.5.17]
at org.apache.ibatis.executor.SimpleExecutor.prepareStatement(SimpleExecutor.java:89) ~[mybatis-3.5.17.jar:3.5.17]
at org.apache.ibatis.executor.SimpleExecutor.doUpdate(SimpleExecutor.java:49) ~[mybatis-3.5.17.jar:3.5.17]
at org.apache.ibatis.executor.BaseExecutor.update(BaseExecutor.java:117) ~[mybatis-3.5.17.jar:3.5.17]
at org.apache.ibatis.executor.CachingExecutor.update(CachingExecutor.java:76) ~[mybatis-3.5.17.jar:3.5.17]
at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:197) ~[mybatis-3.5.17.jar:3.5.17]
... 28 common frames omitted
Caused by: java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30009ms (total=0, active=0, idle=0, waiting=0)
at com.zaxxer.hikari.pool.HikariPool.createTimeoutException(HikariPool.java:686) ~[HikariCP-5.1.0.jar:na]
at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:179) ~[HikariCP-5.1.0.jar:na]
at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:144) ~[HikariCP-5.1.0.jar:na]
at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:127) ~[HikariCP-5.1.0.jar:na]
at org.springframework.jdbc.datasource.DataSourceUtils.fetchConnection(DataSourceUtils.java:160) ~[spring-jdbc-6.2.5.jar:6.2.5]
at org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:118) ~[spring-jdbc-6.2.5.jar:6.2.5]
at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:81) ~[spring-jdbc-6.2.5.jar:6.2.5]
... 36 common frames omitted
Caused by: com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure
The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
at com.mysql.cj.jdbc.exceptions.SQLError.createCommunicationsException(SQLError.java:165) ~[mysql-connector-j-9.1.0.jar:9.1.0]
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:55) ~[mysql-connector-j-9.1.0.jar:9.1.0]
at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:837) ~[mysql-connector-j-9.1.0.jar:9.1.0]
at com.mysql.cj.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:420) ~[mysql-connector-j-9.1.0.jar:9.1.0]
at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:238) ~[mysql-connector-j-9.1.0.jar:9.1.0]
at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:180) ~[mysql-connector-j-9.1.0.jar:9.1.0]
at com.zaxxer.hikari.util.DriverDataSource.getConnection(DriverDataSource.java:137) ~[HikariCP-5.1.0.jar:na]
at com.zaxxer.hikari.pool.PoolBase.newConnection(PoolBase.java:360) ~[HikariCP-5.1.0.jar:na]
at com.zaxxer.hikari.pool.PoolBase.newPoolEntry(PoolBase.java:202) ~[HikariCP-5.1.0.jar:na]
at com.zaxxer.hikari.pool.HikariPool.createPoolEntry(HikariPool.java:461) ~[HikariCP-5.1.0.jar:na]
at com.zaxxer.hikari.pool.HikariPool$PoolEntryCreator.call(HikariPool.java:724) ~[HikariCP-5.1.0.jar:na]
at com.zaxxer.hikari.pool.HikariPool$PoolEntryCreator.call(HikariPool.java:703) ~[HikariCP-5.1.0.jar:na]
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642) ~[na:na]
... 1 common frames omitted
Caused by: com.mysql.cj.exceptions.CJCommunicationsException: Communications link failure
The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
at java.base/jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:62) ~[na:na]
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:502) ~[na:na]
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:486) ~[na:na]
at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:52) ~[mysql-connector-j-9.1.0.jar:9.1.0]
at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:95) ~[mysql-connector-j-9.1.0.jar:9.1.0]
at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:140) ~[mysql-connector-j-9.1.0.jar:9.1.0]
at com.mysql.cj.exceptions.ExceptionFactory.createCommunicationsException(ExceptionFactory.java:156) ~[mysql-connector-j-9.1.0.jar:9.1.0]
at com.mysql.cj.protocol.a.NativeSocketConnection.connect(NativeSocketConnection.java:79) ~[mysql-connector-j-9.1.0.jar:9.1.0]
at com.mysql.cj.NativeSession.connect(NativeSession.java:142) ~[mysql-connector-j-9.1.0.jar:9.1.0]
at com.mysql.cj.jdbc.ConnectionImpl.connectOneTryOnly(ConnectionImpl.java:961) ~[mysql-connector-j-9.1.0.jar:9.1.0]
at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:825) ~[mysql-connector-j-9.1.0.jar:9.1.0]
... 13 common frames omitted
Caused by: java.net.ConnectException: Connection refused: no further information
at java.base/sun.nio.ch.Net.pollConnect(Native Method) ~[na:na]
at java.base/sun.nio.ch.Net.pollConnectNow(Net.java:682) ~[na:na]
at java.base/sun.nio.ch.NioSocketImpl.timedFinishConnect(NioSocketImpl.java:542) ~[na:na]
at java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:592) ~[na:na]
at java.base/java.net.SocksSocketImpl.connect(SocksSocketImpl.java:327) ~[na:na]
at java.base/java.net.Socket.connect(Socket.java:751) ~[na:na]
at com.mysql.cj.protocol.StandardSocketFactory.connect(StandardSocketFactory.java:144) ~[mysql-connector-j-9.1.0.jar:9.1.0]
at com.mysql.cj.protocol.a.NativeSocketConnection.connect(NativeSocketConnection.java:53) ~[mysql-connector-j-9.1.0.jar:9.1.0]
... 16 common frames omitted
2025-04-20T00:26:03.051+08:00 ERROR 3876 --- [hello-activemq] [ntContainer#1-1] c.e.h.a.consumer.TaskQueueConsumer : 记录任务失败时发生异常:
org.mybatis.spring.MyBatisSystemException:
### Error updating database. Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection
### The error may exist in file [E:\hello-world\hello-activemq\target\classes\mapper\TaskRecordMapper.xml]
### The error may involve com.example.hello.activemq.mapper.TaskRecordMapper.insert
### The error occurred while executing an update
### Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection
at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:99) ~[mybatis-spring-3.0.4.jar:3.0.4]
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:347) ~[mybatis-spring-3.0.4.jar:3.0.4]
at jdk.proxy2/jdk.proxy2.$Proxy61.insert(Unknown Source) ~[na:na]
at org.mybatis.spring.SqlSessionTemplate.insert(SqlSessionTemplate.java:224) ~[mybatis-spring-3.0.4.jar:3.0.4]
at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:62) ~[mybatis-3.5.17.jar:3.5.17]
at org.apache.ibatis.binding.MapperProxy$PlainMethodInvoker.invoke(MapperProxy.java:141) ~[mybatis-3.5.17.jar:3.5.17]
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:86) ~[mybatis-3.5.17.jar:3.5.17]
at jdk.proxy2/jdk.proxy2.$Proxy62.insert(Unknown Source) ~[na:na]
at com.example.hello.activemq.service.impl.TaskRecordServiceImpl.recordFailure(TaskRecordServiceImpl.java:35) ~[classes/:na]
at com.example.hello.activemq.consumer.TaskQueueConsumer.processTask(TaskQueueConsumer.java:60) ~[classes/:na]
at com.example.hello.activemq.consumer.TaskQueueConsumer.receive(TaskQueueConsumer.java:42) ~[classes/:na]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:169) ~[spring-messaging-6.2.5.jar:6.2.5]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:119) ~[spring-messaging-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:110) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter.onMessage(MessagingMessageListenerAdapter.java:84) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:790) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:747) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:725) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.doReceiveAndExecute(AbstractPollingMessageListenerContainer.java:333) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.receiveAndExecute(AbstractPollingMessageListenerContainer.java:270) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.invokeListener(DefaultMessageListenerContainer.java:1420) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.executeOngoingLoop(DefaultMessageListenerContainer.java:1410) ~[spring-jms-6.2.5.jar:6.2.5]
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.run(DefaultMessageListenerContainer.java:1287) ~[spring-jms-6.2.5.jar:6.2.5]
at java.base/java.lang.Thread.run(Thread.java:1583) ~[na:na]
Caused by: org.apache.ibatis.exceptions.PersistenceException:
### Error updating database. Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection
### The error may exist in file [E:\hello-world\hello-activemq\target\classes\mapper\TaskRecordMapper.xml]
### The error may involve com.example.hello.activemq.mapper.TaskRecordMapper.insert
### The error occurred while executing an update
### Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection
at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30) ~[mybatis-3.5.17.jar:3.5.17]
at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:199) ~[mybatis-3.5.17.jar:3.5.17]
at org.apache.ibatis.session.defaults.DefaultSqlSession.insert(DefaultSqlSession.java:184) ~[mybatis-3.5.17.jar:3.5.17]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:333) ~[mybatis-spring-3.0.4.jar:3.0.4]
... 24 common frames omitted
Caused by: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection
at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:84) ~[spring-jdbc-6.2.5.jar:6.2.5]
at org.mybatis.spring.transaction.SpringManagedTransaction.openConnection(SpringManagedTransaction.java:77) ~[mybatis-spring-3.0.4.jar:3.0.4]
at org.mybatis.spring.transaction.SpringManagedTransaction.getConnection(SpringManagedTransaction.java:64) ~[mybatis-spring-3.0.4.jar:3.0.4]
at org.apache.ibatis.executor.BaseExecutor.getConnection(BaseExecutor.java:348) ~[mybatis-3.5.17.jar:3.5.17]
at org.apache.ibatis.executor.SimpleExecutor.prepareStatement(SimpleExecutor.java:89) ~[mybatis-3.5.17.jar:3.5.17]
at org.apache.ibatis.executor.SimpleExecutor.doUpdate(SimpleExecutor.java:49) ~[mybatis-3.5.17.jar:3.5.17]
at org.apache.ibatis.executor.BaseExecutor.update(BaseExecutor.java:117) ~[mybatis-3.5.17.jar:3.5.17]
at org.apache.ibatis.executor.CachingExecutor.update(CachingExecutor.java:76) ~[mybatis-3.5.17.jar:3.5.17]
at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:197) ~[mybatis-3.5.17.jar:3.5.17]
... 28 common frames omitted
Caused by: java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30010ms (total=0, active=0, idle=0, waiting=0)
at com.zaxxer.hikari.pool.HikariPool.createTimeoutException(HikariPool.java:686) ~[HikariCP-5.1.0.jar:na]
at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:179) ~[HikariCP-5.1.0.jar:na]
at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:144) ~[HikariCP-5.1.0.jar:na]
at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:127) ~[HikariCP-5.1.0.jar:na]
at org.springframework.jdbc.datasource.DataSourceUtils.fetchConnection(DataSourceUtils.java:160) ~[spring-jdbc-6.2.5.jar:6.2.5]
at org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:118) ~[spring-jdbc-6.2.5.jar:6.2.5]
at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:81) ~[spring-jdbc-6.2.5.jar:6.2.5]
... 36 common frames omitted
Caused by: com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure
The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
at com.mysql.cj.jdbc.exceptions.SQLError.createCommunicationsException(SQLError.java:165) ~[mysql-connector-j-9.1.0.jar:9.1.0]
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:55) ~[mysql-connector-j-9.1.0.jar:9.1.0]
at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:837) ~[mysql-connector-j-9.1.0.jar:9.1.0]
at com.mysql.cj.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:420) ~[mysql-connector-j-9.1.0.jar:9.1.0]
at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:238) ~[mysql-connector-j-9.1.0.jar:9.1.0]
at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:180) ~[mysql-connector-j-9.1.0.jar:9.1.0]
at com.zaxxer.hikari.util.DriverDataSource.getConnection(DriverDataSource.java:137) ~[HikariCP-5.1.0.jar:na]
at com.zaxxer.hikari.pool.PoolBase.newConnection(PoolBase.java:360) ~[HikariCP-5.1.0.jar:na]
at com.zaxxer.hikari.pool.PoolBase.newPoolEntry(PoolBase.java:202) ~[HikariCP-5.1.0.jar:na]
at com.zaxxer.hikari.pool.HikariPool.createPoolEntry(HikariPool.java:461) ~[HikariCP-5.1.0.jar:na]
at com.zaxxer.hikari.pool.HikariPool$PoolEntryCreator.call(HikariPool.java:724) ~[HikariCP-5.1.0.jar:na]
at com.zaxxer.hikari.pool.HikariPool$PoolEntryCreator.call(HikariPool.java:703) ~[HikariCP-5.1.0.jar:na]
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642) ~[na:na]
... 1 common frames omitted
Caused by: com.mysql.cj.exceptions.CJCommunicationsException: Communications link failure
The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
at java.base/jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:62) ~[na:na]
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:502) ~[na:na]
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:486) ~[na:na]
at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:52) ~[mysql-connector-j-9.1.0.jar:9.1.0]
at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:95) ~[mysql-connector-j-9.1.0.jar:9.1.0]
at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:140) ~[mysql-connector-j-9.1.0.jar:9.1.0]
at com.mysql.cj.exceptions.ExceptionFactory.createCommunicationsException(ExceptionFactory.java:156) ~[mysql-connector-j-9.1.0.jar:9.1.0]
at com.mysql.cj.protocol.a.NativeSocketConnection.connect(NativeSocketConnection.java:79) ~[mysql-connector-j-9.1.0.jar:9.1.0]
at com.mysql.cj.NativeSession.connect(NativeSession.java:142) ~[mysql-connector-j-9.1.0.jar:9.1.0]
at com.mysql.cj.jdbc.ConnectionImpl.connectOneTryOnly(ConnectionImpl.java:961) ~[mysql-connector-j-9.1.0.jar:9.1.0]
at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:825) ~[mysql-connector-j-9.1.0.jar:9.1.0]
... 13 common frames omitted
Caused by: java.net.ConnectException: Connection refused: no further information
at java.base/sun.nio.ch.Net.pollConnect(Native Method) ~[na:na]
at java.base/sun.nio.ch.Net.pollConnectNow(Net.java:682) ~[na:na]
at java.base/sun.nio.ch.NioSocketImpl.timedFinishConnect(NioSocketImpl.java:542) ~[na:na]
at java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:592) ~[na:na]
at java.base/java.net.SocksSocketImpl.connect(SocksSocketImpl.java:327) ~[na:na]
at java.base/java.net.Socket.connect(Socket.java:751) ~[na:na]
at com.mysql.cj.protocol.StandardSocketFactory.connect(StandardSocketFactory.java:144) ~[mysql-connector-j-9.1.0.jar:9.1.0]
at com.mysql.cj.protocol.a.NativeSocketConnection.connect(NativeSocketConnection.java:53) ~[mysql-connector-j-9.1.0.jar:9.1.0]
... 16 common frames omitted
2025-04-20T00:26:03.059+08:00 INFO 3876 --- [hello-activemq] [ntContainer#1-1] c.e.h.a.consumer.TaskQueueConsumer : 消息处理结束