Spring Boot实战:InfluxDB 2.x简单教程

0 阅读10分钟

Spring Boot实战:InfluxDB 2.x简单教程

😄生命不息,写作不止

🔥 继续踏上学习之路,学之分享笔记

👊 总有一天我也能像各位大佬一样

🏆 博客首页   @怒放吧德德  To记录领地 @一个有梦有戏的人

🌝分享学习心得,欢迎指正,大家一起学习成长!

转发请携带作者信息  @怒放吧德德(掘金) @一个有梦有戏的人(CSDN)

image-835954_1_20260209_145632.jpg

前言

InfluxDB 2.x是时序数据库的全新迭代版本,相比1.x重构了底层架构,新增Flux查询语言、任务调度、数据可视化、细粒度权限管控等核心功能,采用Bucket(数据桶)+Org(组织)+Token(令牌)的鉴权模式,完全替代了1.x的Database+RP+User模式。本文基于SpringBoot 2.7.x/3.x + InfluxDB 2.7.x稳定版,从零搭建项目,提供完整的时序数据读写、批量操作、Flux查询案例,解决2.x版本适配痛点。

核心差异提醒:InfluxDB 2.x不兼容1.x的influxdb-java客户端,必须改用官方influxdb-client-java;查询语法从InfluxQL切换为Flux,数据存储单元从Database变为Bucket。

本次案例代码仓库:gitee.com/liyongde/ja…

1 环境准备

  • 开发环境:JDK 8+、Maven 3.6+、IntelliJ IDEA
  • 框架版本:SpringBoot 2.7.18(兼容JDK8,3.x需适配Jakarta注解)
  • InfluxDB版本:2.7.1(官方Docker/本地安装均可)
  • 核心依赖:influxdb-client-java(InfluxDB 2.x官方Java客户端)

1.1 安装 InfluxDB2.x

本次安装为了快捷,使用了 docker 安装。

# 拉取镜像
docker pull influxdb:2.1.1

运行容器

docker run --name influxdb -p 8086:8086 --restart always -e DOCKER_INFLUXDB_INIT_USERNAME=admin  -e DOCKER_INFLUXDB_INIT_PASSWORD=sc@123456 --privileged=true -v /home/influxdb/data:/var/lib/influxdb2 -td influxdb:2.1.1
 
## -p 8086:8086    映射influxdb默认端口
## --restart always    开机自启
## -e DOCKER_INFLUXDB_INIT_USERNAME    设置登录用户名
## -e DOCKER_INFLUXDB_INIT_PASSWORD    设置登录密码(8位及以上)
## --privileged=true    对主机系统资源更深层次的访问权限
## -v /home/influxdb/data:/var/lib/influxdb2    映射数据存储路径

访问Web控制台:http://localhost:8086 一开始进入会显示 get start 的按钮然后初始化,具体安装见附录[1]。

1.2 InfluxDB 2.x 初始化准备

  1. 启动InfluxDB 2.x服务,访问Web控制台:http://localhost:8086
  2. 完成初始化:设置用户名、密码、组织名(Org)、数据桶(Bucket),生成All-Access Token
  3. 记录关键信息:Org名称、Bucket名称、All-Access Token(后续配置必填)

2 SpringBoot项目搭建(2.x专属配置)

2.1 引入Maven依赖

剔除1.x客户端,引入InfluxDB 2.x官方客户端,保留SpringBoot基础依赖:

<?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>2.7.18</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>springboot-influxdb2-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-influxdb2-demo</name>
    <description>SpringBoot整合InfluxDB 2.x实战案例</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!-- SpringBoot Web核心依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- InfluxDB 2.x 官方Java客户端 -->
        <dependency>
            <groupId>com.influxdb</groupId>
            <artifactId>influxdb-client-java</artifactId>
            <version>6.11.0</version>
        </dependency>

        <!-- 日期工具类 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.30</version>
        </dependency>

        <!-- 测试依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

2.2 编写InfluxDB 2.x配置文件

application.yml中配置2.x专属连接参数,替换为自己的Token、Org、Bucket:

spring:
  application:
    name: springboot-influxdb2-demo

# InfluxDB 2.x 核心配置
influx:
  # 服务地址
  url: http://127.0.0.1:8086
  # 初始化生成的All-Access令牌
  token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  # 组织名称
  org: my-org
  # 数据桶名称(替代1.x的database)
  bucket: monitor-bucket

2.3 封装InfluxDB 2.x配置类

初始化InfluxDBClient客户端,注入WriteApi(写入)、QueryApi(查询),交由Spring容器管理:

@Configuration
public class InfluxDB2Config {

    @Value("${influx.url}")
    private String url;

    @Value("${influx.token}")
    private String token;

    @Value("${influx.org}")
    private String org;

    @Value("${influx.bucket}")
    private String bucket;

    /**
     * 初始化InfluxDB 2.x客户端
     * 修复:通过OkHttpClient配置超时,替代不存在的set超时方法
     */
    @Bean
    public InfluxDBClient influxDBClient() {
        // 创建客户端,传入自定义OkHttp配置
        InfluxDBClient client = InfluxDBClientFactory.create(
                url,
                token.toCharArray(),
                org,
                bucket
        );

        // 测试连接
        client.ping();
        System.out.println("InfluxDB 2.x 连接成功");
        return client;
    }

    /**
     * 写入API(开启批量写入,提升性能)
     */
    @Bean
    public WriteApi writeApi(InfluxDBClient client) {
        // 批量写入配置:缓冲1000条、500ms刷新、最大5000条
        WriteOptions writeOptions = WriteOptions.builder()
                .batchSize(1000)
                .flushInterval(500)
                .bufferLimit(5000)
                .build();
        return client.makeWriteApi(writeOptions);
    }

    /**
     * 查询API
     */
    @Bean
    public QueryApi queryApi(InfluxDBClient client) {
        return client.getQueryApi();
    }

    /**
     * 项目关闭时销毁客户端
     */
    @PreDestroy
    public void destroy() {
        influxDBClient().close();
    }
}

3 核心业务代码实现

3.1 时序数据实体类

本次通过一个简单存储 PLC 信号值的案例,通过注解映射Measurement、Tag、Field,适配2.x客户端写入规则:

/**
 * plc信号实体
 * @Author: liyongde
 * @Date: 2026/3/24 10:53
 * @Modified By:
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Measurement(name = "plc_signal")
public class PlcSignal {

    /**
     * 设备名称(Tag索引)
     */
    @Column(tag = true, name = "device")
    private String device;

    /**
     * 信号点位/标签(Tag索引)
     */
    @Column(tag = true, name = "tag")
    private String tag;

    /**
     * 信号值(Field,存变化数据)
     */
    @Column(name = "value")
    private String value;

    /**
     * 变化时间戳(InfluxDB自动维护)
     */
    @Column(timestamp = true)
    private Instant changeTime;
}

在 2.x 版本中,Measurement 不需要手动创建,运行的时候会自动生成。

3.2 InfluxDB 2.x工具类

封装单条/批量写入、Flux查询通用方法,简化业务层调用:

/**
 * 工具类
 * @Author: liyongde
 * @Date: 2026/3/24 10:43
 * @Modified By:
 */
@Component
public class InfluxDB2Util {

    @Resource
    private WriteApi writeApi;

    @Resource
    private QueryApi queryApi;

    /**
     * 单条写入PLC信号
     */
    public void insertOne(String bucket, String org, PlcSignal data) {
        writeApi.writeMeasurement(bucket, org, WritePrecision.NS, data);
    }

    /**
     * PLC专用Flux查询结果转换
     */
    public List<PlcSignal> queryPlcFlux(String flux) {
        List<FluxTable> tables = queryApi.query(flux);
        return tables.stream()
                .flatMap(table -> table.getRecords().stream())
                .map(this::convertToPlcEntity)
                .collect(Collectors.toList());
    }

    /**
     * 执行统计查询,返回总数
     * @param flux Flux查询语句
     * @return 统计结果
     */
    public long queryCount(String flux) {
        List<FluxTable> tables = queryApi.query(flux);
        if (tables.isEmpty()) {
            return 0L;
        }
        
        // 获取count结果
        return tables.stream()
                .flatMap(table -> table.getRecords().stream())
                .mapToLong(record -> {
                    Object value = record.getValue();
                    if (value instanceof Number) {
                        return ((Number) value).longValue();
                    }
                    return 0L;
                })
                .sum();
    }

    /**
     * Flux结果转PLC实体
     */
    private PlcSignal convertToPlcEntity(FluxRecord record) {
        return PlcSignal.builder()
                .device(record.getValueByKey("device").toString())
                .tag(record.getValueByKey("tag").toString())
                .value(record.getValueByKey("value").toString())
                .changeTime(record.getTime())
                .build();
    }
}

这里比较麻烦的是需要针对不同的实体进行转换,这个应该是可以自行封装套件来实现自动匹配转换。

3.3 Service层实现

实现数据新增、批量插入、IP查询、全量查询核心业务:

/**
 *
 * @Author: liyongde
 * @Date: 2026/3/24 10:54
 * @Modified By:
 */
@Service
public class PlcSignalService {

    @Value("${influx.bucket}")
    private String bucket;

    @Value("${influx.org}")
    private String org;

    @Resource
    private InfluxDB2Util influxDB2Util;

    /**
     * 本地缓存:存储device+tag对应的最新value,LRU缓存避免内存溢出
     * key: device_tag  value: 最新信号值
     */
    private final LRUCache<String, String> signalCache = CacheUtil.newLRUCache(1000);

    /**
     * 存储PLC变化信号(核心方法:仅存变化值)
     * @param device 设备名
     * @param tag 信号点位
     * @param newValue 新信号值
     * @return 存储结果 true-已存储 false-无变化未存储
     */
    public boolean savePlcChangeSignal(String device, String tag, String newValue) {
        // 1. 构建缓存key
        String cacheKey = device + "_" + tag;
        // 2. 获取历史最新值
        String oldValue = signalCache.get(cacheKey);

        // 3. 判断是否变化:无历史值/值不一致则写入
        if (oldValue == null || !oldValue.equals(newValue)) {
            PlcSignal plcSignal = PlcSignal.builder()
                    .device(device)
                    .tag(tag)
                    .value(newValue)
                    .changeTime(Instant.now())
                    .build();
            // 写入InfluxDB
            influxDB2Util.insertOne(bucket, org, plcSignal);
            // 更新缓存
            signalCache.put(cacheKey, newValue);
            return true;
        }
        // 值无变化,不写入
        return false;
    }

    /**
     * 查询指定设备+标签的历史变化记录
     * @param device 设备名
     * @param tag 信号点位
     * @return 历史变化列表(按时间倒序)
     */
    public List<PlcSignal> getPlcSignalHistory(String device, String tag) {
        // Flux查询语句:查询24小时内指定设备、标签的所有变化数据
        String flux = String.format("from(bucket: \"%s\")\n" +
                        "  |> range(start: -24h)\n" +
                        "  |> filter(fn: (r) => r._measurement == \"plc_signal\" and r.device == \"%s\" and r.tag == \"%s\")\n" +
                        "  |> pivot(rowKey: [\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")\n" +
                        "  |> sort(columns: [\"_time\"], desc: true)",
                bucket, device, tag);
        return influxDB2Util.queryPlcFlux(flux);
    }

    /**
     * 分页查询指定设备+标签的历史变化记录
     * @param device 设备名
     * @param tag 信号点位
     * @param page 页码(从1开始)
     * @param size 每页大小
     * @return 历史变化列表(按时间倒序)
     */
    public List<PlcSignal> getPlcSignalHistoryByPage(String device, String tag, int page, int size) {
        // 计算偏移量
        int offset = (page - 1) * size;
        
        // Flux查询语句:分页查询24小时内指定设备、标签的变化数据
        String flux = String.format("from(bucket: \"%s\")\n" +
                        "  |> range(start: -24h)\n" +
                        "  |> filter(fn: (r) => r._measurement == \"plc_signal\" and r.device == \"%s\" and r.tag == \"%s\")\n" +
                        "  |> pivot(rowKey: [\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")\n" +
                        "  |> sort(columns: [\"_time\"], desc: true)\n" +
                        "  |> limit(n: %d, offset: %d)",
                bucket, device, tag, size, offset);
        return influxDB2Util.queryPlcFlux(flux);
    }

    /**
     * 分页查询指定设备+标签的历史变化记录(支持自定义时间范围)
     * @param device 设备名
     * @param tag 信号点位
     * @param startTime 开始时间(如:-7d, -1h, 2024-03-20T00:00:00Z)
     * @param page 页码(从1开始)
     * @param size 每页大小
     * @return 历史变化列表(按时间倒序)
     */
    public List<PlcSignal> getPlcSignalHistoryByPageWithTimeRange(String device, String tag, String startTime, int page, int size) {
        // 计算偏移量
        int offset = (page - 1) * size;
        
        // Flux查询语句:分页查询指定时间范围内设备、标签的变化数据
        String flux = String.format("from(bucket: \"%s\")\n" +
                        "  |> range(start: %s)\n" +
                        "  |> filter(fn: (r) => r._measurement == \"plc_signal\" and r.device == \"%s\" and r.tag == \"%s\")\n" +
                        "  |> pivot(rowKey: [\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")\n" +
                        "  |> sort(columns: [\"_time\"], desc: true)\n" +
                        "  |> limit(n: %d, offset: %d)",
                bucket, startTime, device, tag, size, offset);
        return influxDB2Util.queryPlcFlux(flux);
    }

    /**
     * 分页查询指定设备+标签的历史变化记录(返回分页结果对象)
     * @param device 设备名
     * @param tag 信号点位
     * @param page 页码(从1开始)
     * @param size 每页大小
     * @return 分页结果对象
     */
    public PageResult<PlcSignal> getPlcSignalHistoryPage(String device, String tag, int page, int size) {
        return getPlcSignalHistoryPageWithTimeRange(device, tag, "-24h", page, size);
    }

    /**
     * 分页查询指定设备+标签的历史变化记录(支持自定义时间范围,返回分页结果对象)
     * @param device 设备名
     * @param tag 信号点位
     * @param startTime 开始时间(如:-7d, -1h, 2024-03-20T00:00:00Z)
     * @param page 页码(从1开始)
     * @param size 每页大小
     * @return 分页结果对象
     */
    public PageResult<PlcSignal> getPlcSignalHistoryPageWithTimeRange(String device, String tag, String startTime, int page, int size) {
        // 1. 查询总数
        long total = getPlcSignalCount(device, tag, startTime);
        
        if (total == 0) {
            return PageResult.empty(page, size);
        }
        
        // 2. 查询当前页数据
        List<PlcSignal> records = getPlcSignalHistoryByPageWithTimeRange(device, tag, startTime, page, size);
        
        // 3. 构建分页结果
        return PageResult.of(records, total, page, size);
    }

    /**
     * 统计指定设备+标签在指定时间范围内的记录总数
     * @param device 设备名
     * @param tag 信号点位
     * @param startTime 开始时间
     * @return 记录总数
     */
    private long getPlcSignalCount(String device, String tag, String startTime) {
        // Flux查询语句:统计记录总数
        String countFlux = String.format("from(bucket: \"%s\")\n" +
                        "  |> range(start: %s)\n" +
                        "  |> filter(fn: (r) => r._measurement == \"plc_signal\" and r.device == \"%s\" and r.tag == \"%s\")\n" +
                        "  |> pivot(rowKey: [\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")\n" +
                        "  |> count(column: \"_time\")",
                bucket, startTime, device, tag);
        
        // 使用专门的统计方法
        return influxDB2Util.queryCount(countFlux);
    }
}

3.4 Controller层接口

提供RESTful接口,方便测试数据读写功能:

@RestController
@RequestMapping("/influx2/plc")
public class PlcSignalController {

    @Resource
    private PlcSignalService plcSignalService;

    /**
     * 接收PLC信号并存储(仅存变化)
     * 入参: {"device":"press","tag":"mode","value":"1"}
     */
    @PostMapping("/save")
    public Map<String, Object> savePlcSignal(@RequestBody Map<String, String> plcData) {
        Map<String, Object> result = new HashMap<>();
        String device = plcData.get("device");
        String tag = plcData.get("tag");
        String value = plcData.get("value");

        // 调用存储方法
        boolean saved = plcSignalService.savePlcChangeSignal(device, tag, value);
        result.put("code", 200);
        result.put("data", saved);
        result.put("msg", saved ? "信号变化,已存储" : "信号无变化,未存储");
        return result;
    }

    /**
     * 查询PLC信号历史变化
     */
    @GetMapping("/history")
        public List<PlcSignal> getPlcHistory(@RequestParam String device, @RequestParam String tag) {
        return plcSignalService.getPlcSignalHistory(device, tag);
    }

    /**
     * 分页查询PLC信号历史变化(24小时内)
     * @param device 设备名
     * @param tag 信号点位
     * @param page 页码(默认1)
     * @param size 每页大小(默认10)
     * @return 分页结果
     */
    @GetMapping("/history/page")
    public PageResult<PlcSignal> getPlcHistoryPage(
            @RequestParam String device, 
            @RequestParam String tag,
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size) {
        return plcSignalService.getPlcSignalHistoryPage(device, tag, page, size);
    }

    /**
     * 分页查询PLC信号历史变化(自定义时间范围)
     * @param device 设备名
     * @param tag 信号点位
     * @param startTime 开始时间(如:-7d, -1h, 2024-03-20T00:00:00Z)
     * @param page 页码(默认1)
     * @param size 每页大小(默认10)
     * @return 分页结果
     */
    @GetMapping("/history/page/range")
    public PageResult<PlcSignal> getPlcHistoryPageWithRange(
            @RequestParam String device, 
            @RequestParam String tag,
            @RequestParam String startTime,
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size) {
        return plcSignalService.getPlcSignalHistoryPageWithTimeRange(device, tag, startTime, page, size);
    }
}

5 项目启动与测试

5.1 启动类

/**
 * 启动类
 * @Author: liyongde
 * @Date: 2026/3/24 10:46
 * @Modified By:
 */
@SpringBootApplication
public class InfluxDB2Application {
    public static void main(String[] args) {
        SpringApplication.run(InfluxDB2Application.class, args);
    }
}

启动之后,同过 postman 或者 apifox 进行测试。

6 总结

本文完整实现了SpringBoot与InfluxDB 2.x的整合,覆盖客户端初始化、注解映射、批量写入、Flux查询全流程,代码可直接部署运行。相比1.x,2.x功能更强大、生态更完善,适配物联网、监控、日志等海量时序数据场景,后续可扩展定时任务、数据聚合、可视化报表等高级功能。

7 附录

[1]: 参考安装: Docker:docker部署influxdb时序数据库 - 怒吼的萝卜 - 博客园


转发请携带作者信息  @怒放吧德德 @一个有梦有戏的人
持续创作很不容易,作者将以尽可能的详细把所学知识分享各位开发者,一起进步一起学习。转载请携带链接,转载到微信公众号请勿选择原创,谢谢!
👍创作不易,如有错误请指正,感谢观看!记得点赞哦!👍
谢谢支持!