Spring Boot 整合 Debezium 实现 MySQL 增量数据监听(嵌入式版)
一、背景与选型在微服务或数据同步场景中我们经常需要实时捕获 MySQL 数据库的增删改操作并触发后续业务逻辑如刷新缓存、同步到 ES、发送消息等。常见的方案有Canal阿里开源Debezium基于 Kafka Connect但也可嵌入式运行Maxwell本文选择Debezium Embedded Engine因为它无需依赖 Kafka可直接在 Spring Boot 应用中内嵌运行支持 MySQL、PostgreSQL、MongoDB 等多种数据库提供完整的变更事件结构before/after、元数据容错性好支持断点续传offset 存储。适用场景中小型项目希望快速集成 CDCChange Data Capture功能又不希望引入额外中间件。二、环境准备2.1 软件版本JDK 17Spring Boot 3.x 要求Spring Boot 3.xMySQL 5.7 或 8.0需开启 binlogDebezium 2.7.x2.2 MySQL 开启 binlog编辑 MySQL 配置文件my.cnf或my.ini添加以下内容[mysqld] log_bin mysql-bin binlog_format ROW binlog_row_image FULL server_id 1 # 确保唯一不能与 Debezium 的 database.server.id 冲突重启 MySQL 服务后执行 SQL 验证SHOW VARIABLES LIKE log_bin; -- 应为 ON SHOW VARIABLES LIKE binlog_format; -- 应为 ROW2.3 创建测试库表和 CDC 用户CREATE USER debezium% IDENTIFIED BY dbz; GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO debezium%; FLUSH PRIVILEGES; -- 创建测试库和表 CREATE DATABASE IF NOT EXISTS park; USE park; CREATE TABLE test_user ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50), age INT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );三、创建 Spring Boot 项目使用 IDEA 或 Spring Initializr 创建一个 Spring Boot 项目引入以下依赖pom.xmlproperties java.version17/java.version debezium.version2.7.0.Final/debezium.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- Debezium 核心 API -- dependency groupIdio.debezium/groupId artifactIddebezium-api/artifactId version${debezium.version}/version /dependency !-- Debezium 嵌入式引擎 -- dependency groupIdio.debezium/groupId artifactIddebezium-embedded/artifactId version${debezium.version}/version /dependency !-- Debezium 文件存储用于 offset 和 schema history -- dependency groupIdio.debezium/groupId artifactIddebezium-storage-file/artifactId version${debezium.version}/version /dependency !-- Debezium MySQL 连接器 -- dependency groupIdio.debezium/groupId artifactIddebezium-connector-mysql/artifactId version${debezium.version}/version /dependency /dependencies四、核心代码编写-可以写在yml里面4.1 配置类DebeziumConfig集中管理连接器配置方便后续调整。package com.example.demo.config; import org.springframework.context.annotation.Configuration; import java.util.Properties; Configuration public class DebeziumConfig { public Properties getDebeziumProperties() { Properties props new Properties(); // 连接器名称 props.setProperty(name, mysql-connector); props.setProperty(connector.class, io.debezium.connector.mysql.MySqlConnector); // 偏移量存储记录消费进度 props.setProperty(offset.storage, org.apache.kafka.connect.storage.FileOffsetBackingStore); props.setProperty(offset.storage.file.filename, ./offsets.dat); props.setProperty(offset.flush.interval.ms, 60000); // 数据库连接 props.setProperty(database.hostname, localhost); props.setProperty(database.port, 3306); props.setProperty(database.user, debezium); props.setProperty(database.password, dbz); props.setProperty(database.server.id, 184054); // 必须唯一 props.setProperty(topic.prefix, dbserver); // 事件主题前缀 // 过滤只监听 park 库下的 test_user 表 props.setProperty(database.include.list, park); props.setProperty(table.include.list, park.test_user); // 时区与 SSL props.setProperty(database.connectionTimeZone, UTC); props.setProperty(database.use.ssl, false); props.setProperty(database.allowPublicKeyRetrieval, true); // Schema 历史存储用于 DDL 变更跟踪 props.setProperty(schema.history.internal, io.debezium.storage.file.history.FileSchemaHistory); props.setProperty(schema.history.internal.file.filename, ./dbhistory.dat); // 快照模式never 表示不执行初始快照只监听增量 props.setProperty(snapshot.mode, never); return props; } }参数说明snapshot.modenever启动后不进行全表快照只监听后续变更。若希望首次启动时先同步现有数据可改为initial。offset.storage.file.filename记录已消费的 binlog 位置重启后从中断处继续。schema.history.internal.file.filename记录表结构变化历史用于正确解析事件中的字段。4.2 监听器组件DebeziumListener负责启动 Debezium 引擎接收并解析变更事件。package com.example.demo.listener; import com.example.demo.config.DebeziumConfig; import com.fasterxml.jackson.databind.ObjectMapper; import io.debezium.engine.ChangeEvent; import io.debezium.engine.DebeziumEngine; import io.debezium.engine.format.Json; import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.io.IOException; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; Component public class DebeziumListener { private static final Logger LOG LoggerFactory.getLogger(DebeziumListener.class); Autowired private DebeziumConfig debeziumConfig; private final ExecutorService executor Executors.newSingleThreadExecutor(); private DebeziumEngineChangeEventString, String engine; private final ObjectMapper objectMapper new ObjectMapper(); PostConstruct public void start() { var props debeziumConfig.getDebeziumProperties(); this.engine DebeziumEngine.create(Json.class) .using(props) .notifying(this::handleChangeEvent) .build(); executor.execute(() - { try { engine.run(); } catch (Exception e) { LOG.error(Debezium engine runtime error: , e); } }); LOG.info(Debezium Engine started asynchronously.); } /** * 处理每个变更事件 */ private void handleChangeEvent(ChangeEventString, String event) { String value event.value(); if (value null) return; try { MapString, Object payload objectMapper.readValue(value, Map.class); MapString, Object payloadData (MapString, Object) payload.get(payload); if (payloadData null) return; // 获取源信息 MapString, Object source (MapString, Object) payloadData.get(source); String db (String) source.get(db); String table (String) source.get(table); // 操作类型cinsert, uupdate, ddelete String operation (String) payloadData.get(op); if (operation null) { LOG.warn(Operation is null, skipping event for {}.{}, db, table); return; } MapString, Object before (MapString, Object) payloadData.get(before); MapString, Object after (MapString, Object) payloadData.get(after); switch (operation) { case c - { LOG.info(插入数据 on {}.{}: {}, db, table, after); // TODO: 业务处理 } case u - { LOG.info(更新数据 on {}.{}: before{}, after{}, db, table, before, after); // TODO: 业务处理 } case d - { LOG.info(删除数据 on {}.{}: {}, db, table, before); // TODO: 业务处理 } default - LOG.debug(Unknown operation: {}, operation); } } catch (IOException e) { LOG.error(Error parsing change event: {}, e.getMessage(), e); } } PreDestroy public void stop() { if (engine ! null) { try { engine.close(); } catch (Exception e) { LOG.error(Error closing Debezium engine, e); } } executor.shutdownNow(); LOG.info(Debezium Engine stopped.); } }注意在handleChangeEvent中你可以注入 Service 层将变更数据同步到 Redis、Elasticsearch 、Mqtt。五、运行与验证5.1 启动 Spring Boot 应用启动主类观察控制台输出Debezium Engine started asynchronously.5.2 在 MySQL 中执行 DML 操作-- 插入 INSERT INTO park.test_user (name, age) VALUES (张三, 25); -- 更新 UPDATE park.test_user SET age 26 WHERE name 张三; -- 删除 DELETE FROM park.test_user WHERE name 张三;5.3 查看应用日志你会看到类似以下格式的事件日志六、进阶说明6.1 数据格式详解Debezium 输出的 JSON 结构大致如下{ payload: { before: { id: 1, name: 张三, age: 25 }, after: { id: 1, name: 张三, age: 26 }, source: { db: park, table: test_user, server_id: 184054, ts_ms: 1721212345678, gtid: null, file: mysql-bin.000001, pos: 1234 }, op: u, ts_ms: 1721212345678 } }opc插入u更新d删除r表示快照若开启。before/after分别为变更前/后的行数据删除操作只有 before。6.2 断点续传原理offsets.dat文件记录了当前消费的 binlog 位置文件名 偏移量。重启应用后Debezium 会从该位置继续读取不会丢失数据。若想重新消费全部数据只需删除offsets.dat和dbhistory.dat并将snapshot.mode改为initial。6.3 多表监听修改table.include.list为多个表用逗号分隔props.setProperty(table.include.list, park.test_user, park.another_table);也可以通过database.include.list监听的库再通过table.exclude.list排除部分表。七、常见问题及解决方法问题现象可能原因解决方案启动时连接 MySQL 失败用户权限不足 / SSL 问题检查用户授权添加database.allowPublicKeyRetrievaltrue无任何变更事件输出binlog 未开启或格式不是 ROW检查 MySQL 配置确认binlog_formatROW事件中 before/after 为 nullbinlog_row_image不是 FULL设置为FULL并重启 MySQL重启后重复消费或漏消费offset 文件损坏删除offsets.dat和dbhistory.dat设置snapshot.modeinitial重新同步解析 JSON 异常表结构变更未正确记录确保schema.history.internal.file.filename文件持久化不要删除database.server.id冲突与 MySQL 的 server_id 或其它连接器重复修改为不同的整数值

相关新闻

AI数字人直播实时美颜与背景替换技术原理分析

AI数字人直播实时美颜与背景替换技术原理分析

背景在AI数字人直播场景中,画面质量直接影响观众停留和转化。实时美颜和背景替换作为两项基础但关键的画面处理技术,其技术选型直接决定了端侧算力开销和渲染延迟。技术原理实时美颜AI直播中的美颜技术主要基于轻量级CNN(卷积神经网络&#x…

2026/10/12 16:31:37 阅读更多 →
一位老馆长的遗憾,成了殡仪馆改造的起点

一位老馆长的遗憾,成了殡仪馆改造的起点

一位殡仪馆的老馆长说过一句话:“我这辈子最后悔的事,就是让成千上万的人在一间冷冰冰的屋子里,跟亲人说了最后一句话。” 他指的是自己馆里的告别厅——白色荧光灯管、灰白色瓷砖墙、成排的硬塑料椅,空调出风口正对着家属座位。 …

2026/10/4 4:52:59 阅读更多 →
报错记录 类型未定义警告

报错记录 类型未定义警告

type of ‘n’ defaults to ‘int’ [-Wimplicit-int]#include<stdio.h> #include<string.h>int fun(int n) {//第一层&#xff1a;5*fun(4)//第二层&#xff1a;4*fun(3)//第三层&#xff1a;3*fun(2)//第四层&#xff1a;2*fun(1)//第五层&#xff1a;return 1if…

2026/10/10 4:29:05 阅读更多 →

最新新闻

孕期婆媳关系转变的心理分析与应对策略

孕期婆媳关系转变的心理分析与应对策略

1. 家庭关系中的角色转变现象怀孕这件事&#xff0c;往往成为检验家庭关系的试金石。许多准妈妈都会发现&#xff0c;从验孕棒显示两道杠的那一刻起&#xff0c;婆婆的态度就像坐上了过山车——孕前可能对你爱答不理&#xff0c;孕后却突然变得嘘寒问暖。这种180度的态度转变&a…

2026/10/14 12:07:10 阅读更多 →
Python+Flask+Vue构建油田土地档案管理系统实践

Python+Flask+Vue构建油田土地档案管理系统实践

1. 项目背景与核心价值油田土地档案管理是能源行业基础设施管理的重要组成部分。传统纸质档案管理方式存在查询效率低、数据易丢失、协同困难等问题。这套基于PythonFlaskVue技术栈的管理系统&#xff0c;正是为了解决这些痛点而生。我在某大型油田企业实施类似系统时发现&…

2026/10/14 12:07:10 阅读更多 →
MyBatis企业级应用实战与性能优化指南

MyBatis企业级应用实战与性能优化指南

1. 项目概述作为一名常年与数据库打交道的Java开发者&#xff0c;我深知CRUD&#xff08;增删改查&#xff09;工作有多么枯燥乏味。每天重复着相似的SQL语句&#xff0c;写着大同小异的DAO层代码&#xff0c;这种"搬砖"式开发不仅消磨热情&#xff0c;更限制了技术成…

2026/10/14 12:07:10 阅读更多 →
图书馆管理系统数据库设计:ER图、数据字典与建表SQL实践

图书馆管理系统数据库设计:ER图、数据字典与建表SQL实践

简介&#xff1a;图书馆管理系统设计文档&#xff0c;涵盖系统需求分析、功能结构图、业务流程图、数据流程图与ER图等核心建模内容&#xff0c;面向软件工程、数据库设计及课程设计场景&#xff0c;旨在解决传统图书馆管理中检索速度慢、借书还书工作量大、图书统计困难等痛点…

2026/10/14 12:07:10 阅读更多 →
Unity深度图原理与实战:从渲染排序到SSR/SSAO应用

Unity深度图原理与实战:从渲染排序到SSR/SSAO应用

1. 什么是Unity深度图&#xff1a;它不是“一张图”&#xff0c;而是渲染管线里最沉默的裁判“Unity深度图”这五个字在刚接触图形学的开发者嘴里&#xff0c;常被误读成“一张存着深度值的贴图”。我带过不少刚从学校出来的实习生&#xff0c;第一反应都是去Project窗口右键新…

2026/10/14 12:07:10 阅读更多 →
听觉-触觉跨模态注意力机制:实现仿生机器人遮挡场景的多模态感知融合

听觉-触觉跨模态注意力机制:实现仿生机器人遮挡场景的多模态感知融合

简介&#xff1a;仿生机器人听觉-触觉跨模态感知融合的527页专题技术文档&#xff0c;聚焦遮挡场景下的鲁棒性设计&#xff0c;面向机器人感知、深度学习和多模态融合方向的研究者与工程师。资源为单个PDF文件&#xff0c;压缩包大小10.75MB&#xff0c;支持目录跳转与书签大纲…

2026/10/14 12:06:10 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起&#xff1a;它到底想解决什么问题第一次看到"AnyPS5"这个标题&#xff0c;我脑子里冒出来的第一个念头是&#xff1a;这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述&#xff1a;Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师&#xff0c;我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性&#xff0c;实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景&#xff0c;也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间&#xff0c;确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中&#xff0c;这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介&#xff1a;基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码&#xff0c;面向计算机相关专业课程设计与期末大作业学生&#xff0c;以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程&#xff0c;…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程&#xff1a;键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化&#xff0c;十个新手有八个栽在"往输入框里填东西"这件事上&#xff1a;要么填不进去&#xff0c;要么填了一半&#xff0c;要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程&#xff1a;阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀&#xff1a;什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面&#xff0c;跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →