Java 后端实战进阶:从踩坑到架构的系统化笔记

Java 后端实战进阶:从踩坑到架构的系统化笔记

副标题:5 年工业级项目实战经验沉淀,覆盖 MySQL 调优、Spring 全家桶、微服务、中间件、线上排障的硬核方法论


专栏简介

本专栏是一位 Java 后端开发工程师在工业互联网、电力行业、物联网等真实项目中积累的 5 年实战精华。不同于学院派的教程式写作,每一篇内容都源自真实生产环境的踩坑、排查、优化与重构经历。

专栏内容严格遵循"问题 → 原因分析 → 解决方案 → 最优实践"的四步闭环方法论,确保每个知识点都经得起生产环境的检验。所有案例均来自实际业务场景,涉及设备联动系统、智能巡视平台、告警中心、数据中台等多个工业级项目。

无论你是刚入行 1~3 年的初中级开发者,还是希望系统梳理实战经验的中高级工程师,本专栏都能为你提供直接可复用的方法论和代码范式。


适配人群

  • 1~3 年 Java 后端开发者:想从 CRUD 跃升到能独立承担复杂模块开发
  • 3~5 年中高级工程师:希望系统化沉淀经验、提升架构思维
  • 转行/自学 Java 的开发者:需要一个有实战视角的进阶指南
  • 技术团队 Lead/架构师:可作为团队 Code Review 和技术培训的参考资料

学习收益

完成本专栏学习后,你将获得:

  1. MySQL 索引优化的系统方法论:掌握索引失效的 11 种场景及其替代方案,能快速定位慢查询
  2. Spring 生态的深度实战能力:理解事务传播、AOP 原理、Bean 生命周期等核心机制
  3. Redis 缓存的正确使用姿势:解决缓存一致性问题,掌握 key 设计策略与批量操作
  4. 线上问题排查的标准思路:建立从日志到 jstack 到 Arthas 的完整排查体系
  5. 高质量代码的设计原则:掌握方法拆分、参数设计、异常处理的最佳实践
  6. 微服务架构的落地经验:从服务拆分到网关设计到分布式事务的完整路径

专栏目录

模块一:MySQL 实战调优(8 篇)

序号 篇名 核心要点
01 索引失效的 11 种场景与替代方案 最左匹配、函数导致失效、% 前缀模糊、类型隐式转换等
02 慢查询排查三板斧:EXPLAIN + 慢查询日志 + Arthas 从定位到优化的标准流程
03 JOIN 优化:INNER JOIN vs LEFT JOIN 的正确选择 小表驱动大表、子查询转连接查询、ON 与 WHERE 的区别
04 批量操作的坑:MySQL 一次能接收多大数据 foreach 性能调优、INSERT 批量优化、分批提交策略
05 数据库设计中的"备用字段"哲学 冗余字段的利弊、宽表 vs 窄表、历史表与流水表设计
06 分库分表实战:从单表到按月分表的演进 文件服务分表案例、数据迁移策略、查询适配方案
07 GROUP BY 深度解析:HAVING、WHERE、ORDER BY 的陷阱 分组查询最新一条数据、GROUP_CONCAT 行转列技巧
08 生产环境 SQL 优化的 7 个黄金法则 覆盖索引回表、UNION vs UNION ALL、FORCE INDEX 的使用时机

模块二:MyBatis / MyBatis-Plus 深度实战(6 篇)

序号 篇名 核心要点
09 MyBatis 动态 SQL 减少 if-else 的艺术 动态 SQL 替代 Java 分支判断、discriminator 鉴别器的使用
10 MyBatis 传参的 10 个隐藏坑 Integer 值为 0 被转为空串、foreach 批量性能、statementType 用法
11 从 MyBatis 迁移到 MyBatis-Plus 的实战收益 代码迁移效率提升、单表 CRUD 标准化、代码生成器的定制
12 PageHelper 分页原理与"只能分页一次"的陷阱 ThreadLocal 拦截机制、为什么 Service 层调用分页会失效
13 MyBatis TypeHandler:解决数据库字段与 Java 类型的映射难题 JSON 类型存储、枚举映射、自定义类型处理器
14 MyBatis-Plus Join 连表查询实战 MPJBaseMapper 使用、连表分页、替代 XML 映射的方案

模块三:Spring 生态深度解析(8 篇)

序号 篇名 核心要点
15 Spring 事务的 5 个诡异场景 检修区域事务问题、事务失效的常见原因、@Transactional 最佳实践
16 Spring AOP 实战:日志切面与统一异常处理 @Around 环绕通知、自定义注解 + 切面的通用方案
17 Bean 生命周期全链路:从 @PostConstruct 到 BeanPostProcessor 初始化回调、Aware 接口、BeanFactoryPostProcessor 的区别
18 Spring Boot 过滤器与拦截器:从原理到最佳实践 Filter vs Interceptor 的本质区别、白名单、防重放、二次鉴权的实现
19 Spring Boot 异步编程:从 @Async 到 CompletableFuture 线程池配置、异步变同步、CountDownLatch 的正确用法
20 Spring Boot 配置管理:@ConfigurationProperties 的正确姿势 与 @Value 的区别、多环境配置、配置热更新
21 Spring Boot 统一返回体设计:一个被低估的基础能力 返回体标准化、错误码体系、全局异常处理器 @RestControllerAdvice
22 Spring Bean 注入避坑指南 @Autowired vs @Resource、头重脚轻问题、循环依赖的 3 种解决方案

模块四:Redis 实战与缓存策略(5 篇)

序号 篇名 核心要点
23 缓存一致性问题的排查与解决 典型案例:变电站配置更新后查询不生效的根因分析
24 Redis Key 设计策略:从命名规范到版本控制 模块化 key 管理、版本号嵌入 key、过期时间设计
25 Redis 批量操作与 Lua 脚本实战 KEYS 命令的风险、批量删除的正确姿势、EVAL 脚本编写
26 缓存穿透、击穿、雪崩的工业级解决方案 布隆过滤器、互斥锁、随机过期时间、请求降级策略
27 Redis 在设备联动系统中的实战应用 点位缓存、配置缓存、告警规则缓存的设计与维护

模块五:线上问题排查方法论(5 篇)

序号 篇名 核心要点
28 线上服务重启后数据丢失的排查 内存队列积压丢失、持久化方案选型、消息可靠性保障
29 jstack + jps 定位线程池爆满问题 xx现场线程泄漏案例、线程 dump 分析方法
30 日志打印的艺术:从排障利器到性能杀手 日志级别控制、@Slf4j topic 分离、时间格式化、入参出参打印规范
31 反向隔离装置调试:网络抓包到防火墙排障 Wireshark 抓包、tcpdump 命令、MAC 地址配置错误排查
32 生产环境 JAR 包不生效的诡异问题 lib 替换不生效、反编译验证、Docker 环境镜像一致性

模块六:高质量代码设计原则(7 篇)

序号 篇名 核心要点
33 方法设计的 5 条军规 单一职责、主方法不超过 30 行、方法调用不超过 3 层
34 参数设计的正反案例 独立参数对象、从外到内 vs 从内到外的组装策略
35 异常处理最佳实践 try-catch 最短路径原则、第三方调用必须 try-catch、错误码体系设计
36 代码重构实战:从面向过程到面向对象 patrol-linkage 重构案例、共性代码提取、策略模式应用
37 设计模式在业务开发中的实战运用 策略模式处理多厂商逻辑、适配器模式对接第三方系统
38 Java 8 Stream API 实战技巧 去重、排序空值安全、collectingAndThen、Optional 防空指针
39 代码鲁棒性:让程序在异常情况下也能优雅运行 输入校验、边界检查、超时重试、防御性编程

模块七:微服务架构落地(6 篇)

序号 篇名 核心要点
40 Spring Cloud Alibaba 核心组件全景 Nacos、Sentinel、Seata、RocketMQ、Gateway 的协同工作
41 Nacos 注册中心与配置中心的实战配置 版本对齐、服务注册、动态配置刷新、命名空间设计
42 API 网关设计:从路由到鉴权 Spring Cloud Gateway 路由配置、统一鉴权过滤器、请求限流
43 分布式事务:Seata AT 模式实战 本地消息表 vs TCC vs Saga 模式选型、异常回滚处理
44 服务间调用:Feign vs RestTemplate vs gRPC 动态代理原理、超时配置、熔断降级策略
45 多系统数据对接的标准化架构 统一数据模型(CDM)、适配器模式、接口幂等性设计

模块八:中间件与工具实战(5 篇)

序号 篇名 核心要点
46 MQTT 在设备信号采集中的实战 发布/订阅模式、消息可靠性、Spring Integration MQTT 集成
47 Netty 字节序处理:大小端转换 readShort vs readShortLE、协议报文解析实战
48 Docker 容器化部署的 10 个踩坑点 日志限制、宿主机挂载、镜像构建、iptables 配置
49 MinIO 文件服务的性能优化 大批量图片上传测试(1 秒 150 张)、预签名 URL、public/private 策略
50 工具链效率提升:Postman、Apifox、IDEA 高级技巧 书签功能、代码生成、接口 Mock、Ctrl+Shift+F 全局搜索

模块九:项目架构与工程实践(5 篇)

序号 篇名 核心要点
51 从规范到系统:电力行业的架构设计方法论 规范文档驱动、系统功能架构梳理
52 接口文档管理的最佳实践 Swagger、Smart-Doc、Torna 对比、接口备注编号规范
53 多项目代码复用与脚手架建设 公共模块抽取、基础框架搭建、包命名规范
54 代码评审 Checklist:30 条实战规则 入参校验、N+1 查询、魔法数字、日志打印、异常处理
55 需求变更应对策略:从小步快跑到数据迁移 联动功能改造案例、新老数据交割、共性代码提取

模块一:MySQL 实战调优


01 | 索引失效的 11 种场景与替代方案

开篇

索引优化是后端开发者性价比最高的技能之一。一次精准的索引调整,可能让一条原本耗时 3 秒的查询降到 10 毫秒以内,效果立竿见影。

但在实际开发中,索引失效的场景比我们想象的多得多。下面总结的 11 种场景,均来自生产环境的真实案例。

场景一览

1. 不满足最左匹配原则

sql 复制代码
-- 假设联合索引 idx_a_b_c (a, b, c)
-- 以下查询不会走索引
SELECT * FROM t WHERE b = 1 AND c = 2;
-- 以下查询会走索引
SELECT * FROM t WHERE a = 1 AND b = 1;
SELECT * FROM t WHERE a = 1;  -- 走索引的最左前缀

替代方案:调整查询条件顺序,确保联合索引的最左列出现在 WHERE 中。

**2. SELECT ***

sql 复制代码
-- 全字段查询无法利用覆盖索引,导致大量回表
SELECT * FROM station_signal WHERE station_id = '123';
-- 优化:只查询索引列
SELECT id, station_id, signal_name FROM station_signal WHERE station_id = '123';

替代方案 :利用覆盖索引,将查询列包含在索引中,避免回表。

3. LIKE 以 % 开头的模糊查询

sql 复制代码
-- 索引失效
SELECT * FROM device WHERE device_name LIKE '%变压器%';
-- 索引生效
SELECT * FROM device WHERE device_name LIKE '变压器%';

替代方案:使用全文索引(FULLTEXT)、ElasticSearch,或业务上避免前置模糊。

4. NOT EXISTS / NOT IN(非主键索引)

sql 复制代码
-- 索引失效
SELECT * FROM device WHERE id NOT IN (SELECT device_id FROM station_device);
-- NOT IN 主键索引可以走索引
SELECT * FROM device WHERE id NOT IN (1, 2, 3);

替代方案:用 LEFT JOIN + IS NULL 替代 NOT EXISTS。

5. ORDER BY 使用不同方向

sql 复制代码
-- 索引失效
SELECT * FROM patrol_task 
WHERE station_id = '123' 
ORDER BY create_time ASC, task_status DESC;

替代方案:统一排序方向,或使用 FORCE INDEX 强制指定索引。

6. OR 关键字前后不全是索引列

sql 复制代码
-- 索引失效
SELECT * FROM device WHERE station_id = '123' OR device_type = 'camera';
-- 索引生效:OR 前后都是索引列
SELECT * FROM device WHERE station_id = '123' OR device_name = 'xxx';

7. VARCHAR 类型未加引号

sql 复制代码
-- MySQL 隐式类型转换导致索引失效
SELECT * FROM device WHERE station_id = 123;  -- station_id 是 VARCHAR 类型
-- 正确写法
SELECT * FROM device WHERE station_id = '123';

生产案例 :三维系统和站端对接时,点位重要等级在三维端是 int 类型,在集控站端是 varchar 类型,导致查询条件隐式转换,索引完全失效。

8. 在索引列上使用函数

sql 复制代码
-- 索引失效
SELECT * FROM patrol_record 
WHERE DATE_FORMAT(create_time, '%Y-%m-%d') = '2024-01-15';
-- 优化:不改索引列,改条件
SELECT * FROM patrol_record 
WHERE create_time >= '2024-01-15 00:00:00' 
  AND create_time < '2024-01-16 00:00:00';

9. 在索引列上做运算

sql 复制代码
-- 索引失效
SELECT * FROM device WHERE id + 1 = 100;
-- 优化
SELECT * FROM device WHERE id = 99;

10. 没有加 WHERE 或 LIMIT 的全表扫描

sql 复制代码
-- 没有 WHERE 条件
SELECT * FROM device ORDER BY create_time DESC;
-- 优化:加 LIMIT
SELECT * FROM device ORDER BY create_time DESC LIMIT 1000;

11. 对不同的索引进行 ORDER BY

sql 复制代码
-- 两个不同的索引列排序,MySQL 只能选择其中一个
SELECT * FROM device 
WHERE station_id = '123' 
ORDER BY device_name, create_time;

替代方案:建立包含排序字段的联合索引。

避坑总结

原则 说明
建联合索引优于多个单索引 能建联合索引,就别建单个索引
删除无用索引 高并发系统要控制索引数量
能用 WHERE 过滤不用 HAVING HAVING 在分组后过滤,性能更差
必要时用 FORCE INDEX 当优化器选择不当时手动干预

02 | 慢查询排查三板斧:EXPLAIN + 慢查询日志 + Arthas

标准排查流程

复制代码
接口响应慢 → 确认是否数据库交互慢(加日志计时)→ 开启慢查询日志 
→ DESC / EXPLAIN 查看执行计划 → 分析索引使用情况 → 优化 SQL

第一步:接口分段计时

java 复制代码
long start = System.currentTimeMillis();
List<Device> devices = deviceMapper.selectByStation(stationId);
log.info("查询设备耗时: {}ms", System.currentTimeMillis() - start);

第二步:查看执行计划

sql 复制代码
EXPLAIN SELECT * FROM patrol_task 
WHERE station_id = '123' AND task_status = 1 
ORDER BY create_time DESC LIMIT 10;

关注字段

  • type:ALL(全表扫描)→ 需要优化
  • key:实际使用的索引
  • rows:预估扫描行数
  • Extra:Using filesort(需要优化排序)、Using temporary(临时表)

第三步:开启慢查询日志

sql 复制代码
-- 查看慢查询配置
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';

-- 开启慢查询
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;  -- 超过 1 秒记录

实战案例:视图导致的缓存命中率低

问题v_station_node 视图没有包含所有变电站的点位数据,导致部分变电站查询返回空值,缓存命中率低。

排查过程

  1. 发现接口响应慢 → 确认是数据库查询慢
  2. EXPLAIN 发现走了全表扫描
  3. 分析发现视图过滤了部分数据
  4. 改为查询主表,缓存命中率大幅提升

经验查询主表始终是基准,基于主表做优化,再用 Redis 加速查询。


03 | JOIN 优化:INNER JOIN vs LEFT JOIN 的正确选择

ON 与 WHERE 的致命区别

sql 复制代码
-- 错误写法:LEFT JOIN 的过滤条件放在 WHERE 中,退化为 INNER JOIN
SELECT t.*, IFNULL(t2.is_config, 0) AS isConfig
FROM as_station_blltree t
LEFT JOIN as_node_config t2 
  ON t.station_id = t2.station_id AND t2.node_id = t.data_id
WHERE t.station_id = '123' 
  AND t2.is_config = 1;  -- 这一行会导致 LEFT JOIN 变 INNER JOIN

-- 正确写法:LEFT JOIN 的过滤条件放在 ON 中
SELECT t.*, IFNULL(t2.is_config, 0) AS isConfig
FROM as_station_blltree t
LEFT JOIN as_node_config t2 
  ON t.station_id = t2.station_id 
  AND t2.node_id = t.data_id
  AND t2.is_config = 1  -- 条件放在 ON 中
WHERE t.station_id = '123';

核心原则WHERE 子句在连接完成后过滤,会把 LEFT JOIN 变成 INNER JOIN 的效果。需要保留左表全部数据时,过滤条件必须放在 ON 子句中。

小表驱动大表

sql 复制代码
-- 优化前:大表在前面
SELECT * FROM patrol_record r  -- 100 万行
INNER JOIN station s ON r.station_id = s.id;  -- 100 行

-- 优化后:小表在前面
SELECT * FROM station s  -- 100 行
INNER JOIN patrol_record r ON s.id = r.station_id;  -- 100 万行

避坑总结

  1. 能用 INNER JOIN 不用 LEFT JOIN,除非业务确实需要保留左表全部数据
  2. 多个 INNER JOIN 的顺序不影响结果,但影响性能
  3. 子查询转连接查询可以显著提升性能
  4. for 循环里写查询是最费劲的------阿里巴巴开发规范明确禁止

04 | 批量操作的坑:MySQL 一次能接收多大数据

批量插入的 MySQL 限制

java 复制代码
// MyBatis foreach 批量插入
INSERT INTO patrol_signal (station_id, signal_name, signal_code)
VALUES
<foreach collection="list" item="item" separator=",">
  (#{item.stationId}, #{item.signalName}, #{item.signalCode})
</foreach>

问题 :当 list 包含上万条数据时,MySQL 的 max_allowed_packet 参数限制了单次请求的最大数据量,超出后会直接报错。

解决方案:分批提交,每批 500~1000 条。

java 复制代码
// 分批插入
List<List<Signal>> batches = Lists.partition(signals, 500);
for (List<Signal> batch : batches) {
    signalMapper.batchInsert(batch);
}

foreach 的性能问题

MyBatis 的 <foreach> 会对每个参数做占位符替换,当参数量过大时:

  1. SQL 语句过长,解析慢
  2. MySQL 的预编译缓存无法复用

优化方案 :MyBatis-Plus 的 saveBatch 方法内部已实现分批。

java 复制代码
// MyBatis-Plus 批量插入,自动分批
signalService.saveBatch(signals, 500);

UPDATE 批量操作

sql 复制代码
-- 同时 UPDATE 多条记录(CASE WHEN 方式)
UPDATE device 
SET device_status = CASE 
  WHEN id = 1 THEN 'online'
  WHEN id = 2 THEN 'offline'
  ELSE device_status 
END
WHERE id IN (1, 2, 3);

-- REPLACE INTO / ON DUPLICATE KEY UPDATE
INSERT INTO patrol_config (station_id, config_key, config_value)
VALUES ('123', 'interval', '30')
ON DUPLICATE KEY UPDATE config_value = VALUES(config_value);

05 | 数据库设计中的"备用字段"哲学

冗余字段的利弊

在对接第三方系统时,备用字段(extra / extend / reserve)是救命的

sql 复制代码
CREATE TABLE device_info (
    id BIGINT PRIMARY KEY,
    station_id VARCHAR(64),
    device_name VARCHAR(255),
    device_type INT,
    manufacturer VARCHAR(128),
    extra_attr JSON  -- 存储厂家特有属性
);

JSON 字段查询

sql 复制代码
-- MySQL 5.7+ 支持 JSON 字段查询
SELECT * FROM device_info 
WHERE JSON_SEARCH(extra_attr, 'one', 'camera') IS NOT NULL;

宽表 vs 窄表

窄表(标准化设计):一个页面用到多张表的属性,每张表只存自己的属性。查询时需要多表 JOIN。

宽表(反范式设计):将常用字段冗余到一张表,减少 JOIN 次数,提高查询效率。

实战经验 :宽表在查询场景下确实方便,但在更新场景下存在数据一致性问题。选择宽表还是窄表,取决于读多还是写多

流水表与实时表

表类型 用途 特点
流水表 存储全量历史数据 只增不改
实时表 存储最新状态 覆盖更新
历史表 按月/年归档 减少主表数据量
sql 复制代码
-- 文件服务分表示例
pt_upload_file           -- 当前月
pt_upload_file_202401    -- 历史数据
pt_upload_file_202402

08 | 生产环境 SQL 优化的 7 个黄金法则

  1. 索引优化是见效最快的方法,但后续仍需改代码
  2. 覆盖索引减少回表:将 SELECT 列包含在索引中
  3. UNION ALL 替代 UNION:UNION 会做去重操作,性能开销大
  4. 能用 WHERE 过滤不用 HAVING:HAVING 在分组后过滤
  5. EXISTS 替代 IN (大表场景):WHERE EXISTS (SELECT 1 FROM ...) 效率更高
  6. 小表驱动大表:嵌套查询中,外层应该是小表
  7. 通过 SQL 处理的逻辑,随着数据量增长都会变慢------唯一能做的是保证单个 SQL 的稳定性

核心认知:SQL 写得好,下班下得早。


模块二:MyBatis / MyBatis-Plus 深度实战


09 | MyBatis 动态 SQL 减少 if-else 的艺术

动态 SQL 替代 Java 分支判断

正面案例(MyBatis 动态 SQL):

xml 复制代码
<select id="selectByCondition" resultType="DeviceDTO">
    SELECT * FROM device
    <where>
        <if test="stationId != null and stationId != ''">
            AND station_id = #{stationId}
        </if>
        <if test="status != null">
            AND status = #{status}
        </if>
    </where>
    ORDER BY create_time DESC
</select>

<where> 标签会自动处理第一个 AND,比 WHERE 1=1 更优雅。

discriminator 鉴别器

xml 复制代码
<resultMap id="deviceMap" type="Device">
    <discriminator column="device_type" javaType="int">
        <case value="1" resultType="CameraDevice"/>
        <case value="2" resultType="RobotDevice"/>
        <case value="3" resultType="UAVDevice"/>
    </discriminator>
</resultMap>

10 | MyBatis 传参的 10 个隐藏坑

坑 1:Integer 值为 0 被转为空串

xml 复制代码
<!-- 当 param 值为 0 时,test="param != ''" 仍然为 true -->
<if test="param != null and param != ''">
    AND status = #{param}
</if>

原因 :MyBatis 对 Integer 类型做 OGNL 表达式判断时,0 != '' 结果为 true。

解决方案 :对 Integer 类型只判断 != null

坑 2:Mapper 传多个参数

java 复制代码
// 方式一:@Param 注解
List<Device> selectByPage(
    @Param("stationId") String stationId, 
    @Param("pageNum") int pageNum
);

11 | 从 MyBatis 迁移到 MyBatis-Plus 的实战收益

维度 MyBatis MyBatis-Plus
单表 CRUD 手写 XML 自动生成
代码迁移 Mapper XML 与实体绑定 更好的解耦,迁移更灵活
字段变更 修改多处 XML 只改实体类
分页 手动集成 PageHelper 内置分页

实战心得:用 MyBatis-Plus 代码更好迁移。代码转换的地方比较高效。


模块三:Spring 生态深度解析


15 | Spring 事务的 5 个诡异场景

场景 1:同类方法调用事务失效

java 复制代码
@Service
public class DeviceService {
    public void batchUpdate(List<Device> devices) {
        for (Device d : devices) {
            updateOne(d);  // 直接调用同类方法,@Transactional 不生效
        }
    }
    
    @Transactional
    public void updateOne(Device device) {
        deviceMapper.update(device);
    }
}

解决方案:注入自身代理或拆到不同 Service。

场景 2:try-catch 吞掉异常

java 复制代码
@Transactional
public void transfer(TransferDTO dto) {
    try {
        accountMapper.deduct(dto.getFromId(), dto.getAmount());
        accountMapper.add(dto.getToId(), dto.getAmount());
    } catch (Exception e) {
        // 异常被 catch 吞掉,事务不会回滚
    }
}

解决方案:catch 后手动回滚。


18 | Spring Boot 过滤器与拦截器

Filter vs Interceptor 本质区别

维度 Filter Interceptor
规范 Servlet 规范 Spring 框架
实现原理 函数回调 动态代理
作用范围 所有请求 仅 Controller
配置方式 web.xml 或 @Component WebMvcConfigurer

21 | Spring Boot 统一返回体设计

java 复制代码
@Data
@Builder
public class Result<T> {
    private int code;
    private String msg;
    private T data;
    
    public static <T> Result<T> success(T data) {
        return Result.<T>builder().code(200).msg("success").data(data).build();
    }
    
    public static <T> Result<T> error(int code, String msg) {
        return Result.<T>builder().code(code).msg(msg).build();
    }
}

全局异常处理

java 复制代码
@RestControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(BusinessException.class)
    public Result<?> handleBusiness(BusinessException e) {
        return Result.error(e.getCode(), e.getMessage());
    }
    
    @ExceptionHandler(Exception.class)
    public Result<?> handleException(Exception e) {
        return Result.error(500, "系统繁忙,请稍后重试");
    }
}

模块四:Redis 实战与缓存策略


23 | 缓存一致性问题的排查与解决

典型案例

现象 :变电站增加所属组织分组后,查询一直不生效。
根因:更新数据库后没有清除对应缓存。

解决方案

java 复制代码
public void updateStationGroup(String stationId, String groupId) {
    stationMapper.updateGroup(stationId, groupId);
    String cacheKey = "station:detail:" + stationId;
    redisTemplate.delete(cacheKey);
}

缓存更新策略

策略 适用场景 一致性
Cache Aside 读多写少 最终一致
Write Through 写多读少 强一致
Write Behind 高并发写入 最终一致

27 | Redis 在设备联动系统中的实战应用

批量删除 Redis Key

java 复制代码
// Lua 脚本批量删除
String luaScript = "local keys = redis.call('keys', ARGV[1]) " +
    "for i=1,#keys,5000 do " +
    "redis.call('del', unpack(keys, i, math.min(i+4999, #keys))) " +
    "end return #keys";

redisTemplate.execute(
    new DefaultRedisScript<>(luaScript, Long.class),
    Collections.emptyList(),
    "dsa:file-id:*"
);

项目经验:redis key 的管理要特别注意,不然后期项目没法维护。MQ 同样也需要管理起来。


模块五:线上问题排查方法论


29 | jstack + jps 定位线程池爆满问题

生产案例:xx现场线程泄漏

bash 复制代码
# 1. 查看Java进程
jps -l
# 2. 打印线程栈
jstack 35184 > thread_dump.log
# 3. 分析
grep -c "BLOCKED" thread_dump.log

根因:不断创建新线程,没有使用线程池复用。


30 | 日志打印的艺术

java 复制代码
// 使用 topic 分离不同模块日志
@Slf4j(topic = "ROBOT_INFO")
public class RobotService {
    log.info("机器人信号上报: {}", signal);
}

@Slf4j(topic = "LINKAGE_ERROR")
public class LinkageService {
    log.error("联动执行失败: {}", errorMsg);
}

模块六:高质量代码设计原则


33 | 方法设计的 5 条军规

  1. 单一职责:一个方法只干一件事
  2. 主方法不超过 30 行
  3. 方法调用不超过 3 层:Controller → Service → Mapper
  4. 参数用独立对象,不耦合继承关系
  5. 凡靠日志说明业务的,十之八九是方法没拆够细

35 | 异常处理最佳实践

try-catch 最短路径原则

java 复制代码
// 好:在可能出错的最近处 try-catch
queryDevices();
assembleData();
try {
    sendInstruction();  // 调用第三方,最可能出错
} catch (Exception e) {
    log.error("指令发送失败", e);
    updateStatus(taskId, "SEND_FAILED");
}

第三方调用必须 try-catch

涉及第三方的都要 try-catch,不然报错没日志。


36 | 代码重构实战

好的代码是设计 出来的,不是从头到尾写出来的。在一个大系统里面抽丝剥茧然后出设计再重构升级才是真大佬。


模块七:微服务架构落地


40 | Spring Cloud Alibaba 核心组件全景

组件 作用 替代方案
Nacos 服务注册 + 配置中心 Eureka + Config
Sentinel 限流熔断 Hystrix
Seata 分布式事务 手动补偿
RocketMQ 消息队列 Kafka
Gateway API 网关 Zuul

45 | 多系统数据对接的标准化架构

统一入口 + 适配器模式

java 复制代码
@PostMapping("/api/data/receive")
public Result<?> receiveData(@RequestBody DataRequest request) {
    String adapterId = request.getAdapterId();
    DataAdapter adapter = adapterFactory.getAdapter(adapterId);
    return adapter.process(request.getBody());
}

模块八 & 九:中间件与工程实践(精华要点)


49 | MinIO 文件服务性能优化

yaml 复制代码
spring:
  servlet:
    multipart:
      max-file-size: 100MB
      max-request-size: 100MB

51 | 规范驱动的架构设计

电力行业项目基于规范文档开发:阅读规范 → 提取功能要求 → 设计系统架构 → 可持续迭代。


54 | 代码评审 Checklist

  • 入参做非空校验
  • 不在 for 循环里写查询
  • 批量操作分批提交
  • 第三方调用必须 try-catch
  • 不使用魔法数字
  • Redis key 包含模块前缀
  • 数据更新后清除缓存

附录:开发方法论速查

SQL 编写口诀

复制代码
能用 INNER JOIN 不用 LEFT JOIN
能用 WHERE 不用 HAVING
能用 UNION ALL 不用 UNION
能用覆盖索引不做回表

代码质量口诀

复制代码
方法只做一件事,主方法不超三十行
参数封装成对象,入参出参全打印
第三方必 try-catch,异常不吞就近处理

排查问题口诀

复制代码
分段计时定瓶颈,EXPLAIN 看执行计划
日志分级 topic 分,入参出参全记录
jstack 看线程状态,tcpdump 抓网络包

声明:本专栏所有案例均来自真实生产环境实战经验,涉及的业务场景做了脱敏处理。

相关推荐
wuminyu1 小时前
Java锁机制之park与futex系统级协同机制解析
java·linux·c语言·jvm·c++
疯狂打码的少年1 小时前
编译程序与解释程序的区别
java·开发语言·笔记
xieliyu.8 小时前
Java算法精讲:双指针(三)
java·开发语言·算法
love530love8 小时前
LiveTalking 数字人项目 Windows 部署完全指南(EPGF 架构)
人工智能·windows·python·架构·livetalking·epgf
明夜之约8 小时前
Spring Boot 自动装配源码
java·spring boot·后端
Leaton Lee8 小时前
Spring Boot分层架构详解:从Controller到Service再到Mapper的完整流程
java·spring boot·后端·架构
Jinkxs8 小时前
Resilience4j- 与 Spring Boot 快速集成:自动配置与基础注解使用
java·spring boot·后端
辣机小司8 小时前
【踩坑记录:Spring Boot 配置文件读取值不一致?警惕 YAML 的“八进制陷阱”与 SnakeYAML 版本之谜】
java·spring boot·后端·yaml·踩坑记录
fangdengfu1239 小时前
ES分析系统各个服务日志占用量
java·前端·elasticsearch