简介这份源码面向物联网与环境监测方向的Java开发者及课程设计、毕业设计学生提供一套基于Java与阿里云数据库的水质检测系统完整实现解决传感器数据采集、云端存储与远程控制电机的联动问题。压缩包共97个文件、约1.71MB以35个XML配置、27个Java源文件为核心另有19张PNG界面素材、3个Gradle构建脚本、3个JAR依赖包及2个properties属性文件并附readme说明目录结构清晰便于按模块阅读与二次开发。系统实现了MySQL数据库连接取数、用户注册登录修改注销、温度与水质传感器数据展示以及发送命令操控换水电机下位机可通过NB开发板连接阿里云数据库上传保存数据形成采集、处理、展示到控制的闭环。目前已有329人学习下载适合需要快速理解物联网水质监测架构、复用数据库与设备通信代码的读者参考。1. 水质检测系统源码拆解Java 后端 阿里云数据库到底怎么落地很多做课程设计或接私活的兄弟一看到「水质检测系统」就头大传感器数据怎么进来、Java 后端怎么扛住高频写入、阿里云数据库选 RDS 还是 Lindorm、源码拿到手跑不起来怎么办。我前两年帮一个环保监测小团队搭过一套从 pH、浊度、溶解氧三个探头到云端报表踩过的坑比写过的接口还多。这篇就把「基于 Java 和阿里云数据库的水质检测系统」从选型到跑通讲透源码结构、建表语句、采集接口、告警逻辑都给到能抄的程度。适合正在做课程设计的学生、接物联网小项目的 Java 工程师以及想把这套东西产品化的创业者。核心结论先放这Java 负责业务与并发阿里云数据库负责时序存储与聚合两者边界划清楚系统才稳。2. 选型先立住为什么是 Java 加阿里云数据库而不是别的组合2.1 水质检测场景对后端和存储的真实诉求水质检测不是简单的增删改查。一个中等规模监测点每 5 秒上报一次 pH、浊度、温度、溶解氧四个指标一天就是 69120 条记录十个点位就是近 70 万条。这种数据有三个特点写入远大于读取、查询集中在时间范围聚合、历史数据要能按点位和设备维度下钻。用 MySQL 单表硬扛三个月后查询就开始卡用文件存聚合统计又没法做。所以存储层必须支持时序优化和自动冷热分离。Java 在这个场景里的优势不是语言本身多快而是生态成熟。Spring Boot 起服务、MyBatis 或 JPA 做持久化、定时任务做聚合、WebSocket 推实时大屏这些组件随便一个 Java 工程师都能上手。更关键的是阿里云数据库的 Java SDK 和连接池适配做得早Druid、HikariCP 直接配不用自己造轮子。常见做法是采集层用 Netty 或 MQTT 客户端收数据业务层用 Spring Boot 处理存储层按数据热度分 MySQL 和时序库。2.2 阿里云数据库选型RDS MySQL 还是 Lindorm这是问得最多的问题。我的建议很直接如果只是课程设计或小规模试点RDS MySQL 加分区表足够如果要上生产、点位超过 20 个、保留一年以上历史数据直接上 Lindorm 时序引擎。两者的差别不在价格在写入模型和查询效率。对比项RDS MySQLLindorm 时序引擎写入吞吐单实例约 5000 TPS单节点可达 10 万 TPS时间范围聚合依赖索引数据量大后慢原生时间线索引毫秒级冷热分离需手动归档自动分层存储成本低配便宜高配贵按量付费长期更省适用场景课程设计、Demo生产环境、多点位选 RDS 的话表必须按时间分区否则三个月后必翻车。选 Lindorm 的话建表时指定时间戳列和标签列写入直接用 SDK 的 TableStore 接口。我一般会先问对方数据保留多久、点位多少再给结论不盲目推贵的。2.3 源码工程结构怎么读才不迷路拿到一份水质检测系统源码先别急着跑。按这个顺序看先看 pom.xml 或 build.gradle确认 Spring Boot 版本和数据库驱动再看 application.yml找数据库连接和 MQTT 配置然后看 entity 包对应数据库表结构最后看 controller 和 service理清数据流。常见结构是 com.water.monitor 下面分 controller、service、mapper、entity、config、task 六个包。如果源码里没有 task 包说明定时聚合功能缺失得自己补。提示源码里如果出现硬编码的 AccessKey先改成环境变量再跑这是最常见的翻车点。3. 从零跑通建库建表、采集接口和聚合任务的完整步骤3.1 阿里云数据库建库建表时序表结构设计先在阿里云控制台创建 RDS MySQL 实例或 Lindorm 实例拿到内网地址、端口、账号密码。然后建库 water_monitor字符集用 utf8mb4。核心表三张设备表 device、原始数据表 water_data、小时聚合表 water_hourly。原始数据表按天分区避免单表过大。-- 设备表记录点位基本信息 CREATE TABLE device ( id BIGINT PRIMARY KEY AUTO_INCREMENT, device_code VARCHAR(64) NOT NULL UNIQUE COMMENT 设备编号, location VARCHAR(128) COMMENT 安装位置, status TINYINT DEFAULT 1 COMMENT 1在线 0离线, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 原始数据表按天分区存每次上报的指标 CREATE TABLE water_data ( id BIGINT AUTO_INCREMENT, device_code VARCHAR(64) NOT NULL, ph DECIMAL(4,2) COMMENT pH值, turbidity DECIMAL(6,2) COMMENT 浊度NTU, temperature DECIMAL(4,1) COMMENT 温度, dissolved_oxygen DECIMAL(4,2) COMMENT 溶解氧mg/L, report_time DATETIME NOT NULL, PRIMARY KEY (id, report_time), KEY idx_device_time (device_code, report_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 PARTITION BY RANGE (TO_DAYS(report_time)) ( PARTITION p202501 VALUES LESS THAN (TO_DAYS(2025-02-01)), PARTITION p202502 VALUES LESS THAN (TO_DAYS(2025-03-01)), PARTITION pmax VALUES LESS THAN MAXVALUE );建表逻辑说明device 表用 device_code 做唯一键防止重复注册。water_data 表把 report_time 放进主键配合分区键保证按时间查询能命中分区。索引 idx_device_time 覆盖设备加时间的联合查询这是大屏最常用的查询模式。参数上ph 用 DECIMAL(4,2) 是因为 pH 范围 0 到 14两位小数够用浊度可能到几百所以给到 6 位。分区策略按天实际部署时用定时任务每月自动加分区别用 MAXVALUE 兜底太久。3.2 Java 采集接口接收 MQTT 上报并写入数据库采集层用 MQTT 协议最常见探头通过网关发到阿里云 IoT 平台或自建 EMQXJava 后端订阅主题拿数据。下面是一个最小可用的 MQTT 订阅加写入服务用 Spring Boot 加 Eclipse Paho 客户端。Component public class MqttCollector { Value(${mqtt.broker}) private String broker; Value(${mqtt.topic}) private String topic; Autowired private WaterDataMapper waterDataMapper; PostConstruct public void subscribe() throws MqttException { MqttClient client new MqttClient(broker, MqttClient.generateClientId()); MqttConnectOptions options new MqttConnectOptions(); options.setAutomaticReconnect(true); // 断线自动重连 options.setCleanSession(true); client.connect(options); client.subscribe(topic, (t, msg) - { String payload new String(msg.getPayload()); WaterData data JSON.parseObject(payload, WaterData.class); data.setReportTime(new Date()); waterDataMapper.insert(data); // 写入阿里云数据库 }); } }逻辑说明PostConstruct 保证服务启动就订阅。setAutomaticReconnect(true) 是必须的现场网络抖动很常见不加重连会丢数据。回调里直接解析 JSON 并插入生产环境建议先丢进本地队列再批量写避免数据库瞬时压力。参数上broker 地址填阿里云 IoT 的接入点或自建 EMQX 地址topic 按设备编号分层比如 water/{deviceCode}/data。WaterDataMapper 用 MyBatis 生成insert 语句对应 water_data 表。3.3 定时聚合任务把原始数据压成小时报表原始数据量大大屏查询不能直接扫全表。用 Spring 的 Scheduled 每小时跑一次聚合把 water_data 按设备和小时汇总到 water_hourly。Component public class AggregateTask { Autowired private WaterDataMapper waterDataMapper; Scheduled(cron 0 5 * * * ?) // 每小时第5分钟执行 public void hourlyAggregate() { String sql INSERT INTO water_hourly (device_code, hour_time, avg_ph, avg_turbidity) SELECT device_code, DATE_FORMAT(report_time, %Y-%m-%d %H:00:00), AVG(ph), AVG(turbidity) FROM water_data WHERE report_time DATE_SUB(NOW(), INTERVAL 1 HOUR) GROUP BY device_code, DATE_FORMAT(report_time, %Y-%m-%d %H:00:00); waterDataMapper.executeAggregate(sql); } }逻辑说明cron 表达式 0 5 * * * ? 表示每小时第 5 分钟执行留出数据落库时间。SQL 用 INSERT INTO SELECT 直接在数据库端聚合比拉到 Java 内存算快得多。参数上DATE_SUB(NOW(), INTERVAL 1 HOUR) 限定只处理上一小时数据避免重复聚合。如果用的是 Lindorm聚合可以用其内置的降采样功能不用自己写 SQL。4. 避坑与排查水质检测系统上线后最容易翻车的 5 个点4.1 数据库连接池耗尽接口大面积超时现象系统跑几天后采集接口开始报 Connection timeout重启就好过几天又犯。原因MQTT 回调里每次插入都新建连接或者连接池最大连接数设太小而采集频率高连接来不及释放。解决用 HikariCP 统一管理maximumPoolSize 设成 CPU 核数乘 2 再加磁盘数一般 20 到 50 够用回调里用批量插入每 100 条或每秒刷一次减少连接占用。4.2 时间字段时区不对聚合结果差 8 小时现象大屏上显示的小时数据对不上明明下午三点的数据跑到了晚上十一点。原因阿里云 RDS 默认时区可能是 UTCJava 应用用东八区写入和查询时区不一致。解决在 JDBC URL 里加 serverTimezoneAsia/Shanghai同时数据库连接后执行 SET time_zone 8:00。建表时 DATETIME 不存时区全靠应用层统一。4.3 分区表没自动维护写入报错现象月初第一天采集数据全部写入失败日志报 No partition for value。原因RANGE 分区只建到当月新月份数据没有对应分区。解决写一个定时任务每月 25 号自动添加下个月分区用 ALTER TABLE water_data ADD PARTITION 语句。或者直接用 Lindorm不用操心分区。4.4 源码里的 MQTT 客户端版本冲突现象项目启动报 NoSuchMethodError指向 Paho 客户端。原因源码里同时引入了 org.eclipse.paho.client.mqttv3 和阿里云 IoT SDK 自带的 MQTT 库版本不一致。解决在 pom.xml 里用 exclusion 排除冲突依赖只保留一个版本。我一般统一用 Paho 1.2.5稳定且文档多。4.5 告警逻辑写在回调里阻塞采集线程现象pH 超标时发邮件或短信结果采集延迟越来越大。原因告警发送是同步 HTTP 调用耗时几百毫秒卡在 MQTT 回调线程里。解决回调只负责入库告警判断和发送丢到独立线程池或消息队列用 Async 异步处理。线程池核心数设 4队列容量 1000满了就丢弃并记日志。5. 进阶技巧用阿里云数据库的降采样和 Java 异步流做实时大屏5.1 用 Lindorm 降采样替代手写聚合 SQL如果用的是 Lindorm 时序引擎建表时直接指定降采样规则比如每 5 分钟、每 1 小时自动聚合查询时指定精度就行不用自己写 INSERT INTO SELECT。建表语句里加 DOWNSAMPLE 子句写入原始数据后系统自动生成聚合结果。这样 Java 端只负责写入和查询聚合逻辑下沉到数据库代码少一半延迟还低。5.2 Java 异步流推 WebSocket 大屏大屏要实时刷新用 Spring WebSocket 加 Reactor 的 Flux 做推送。每次聚合任务完成后查一次最新小时数据通过 WebSocket 广播给前端。关键点是别在 WebSocket 里做数据库查询而是聚合任务主动推。代码上用 SimpMessagingTemplate.convertAndSend 往指定 topic 发消息前端订阅即可。这样大屏刷新频率和聚合频率解耦数据库压力可控。5.3 验证方法用压测脚本确认写入上限上线前用 JMeter 或自己写个 Java 压测程序模拟 50 个设备每秒上报一次跑 10 分钟看数据库写入延迟和连接池状态。重点看两个指标water_data 表的插入耗时是否稳定在 10ms 以内HikariCP 的 activeConnections 是否长期占满。如果占满先加批量写入再考虑升配。我一般会留 30% 余量别等打满才扩容。这套东西我前后调了三个月最大的教训是别一上来就追求大而全。先把采集和入库跑通再加聚合和告警最后做大屏。源码只是起点数据库选型和参数调优才是能不能长期跑的关键。希望帮到你。本文还有配套的精品资源点击获取