当前位置: 首页
编程语言
SpringBoot集成TDengine的完整使用指南

SpringBoot集成TDengine的完整使用指南

热心网友 时间:2026-06-10
转载

一、什么是 TDengine

先说说几个关键点。TDengine 是一款高性能、分布式时序数据库,专为物联网、工业互联网、金融监控等场景量身打造。它到底有多强?来看看它的核心特性。

SpringBoot集成TDengine的完整指南

  • 高性能写入:单表每秒可以达到百万级数据写入,这速度,对于海量设备数据来说,简直不要太爽。
  • 高压缩比:时序数据专用压缩算法,让存储空间节省 90% 以上,成本控制这块拿捏得死死的。
  • 超级表设计:一次定义数据结构,就能自动为每个设备创建自表,这个设计思路相当巧妙。
  • 标签查询:支持按设备类型、位置等维度快速聚合,让数据筛选变得简单直接。
  • SQL 语法:采用类 SQL 语法,学习成本低,团队上手非常快。

二、快速开始

那么,我们需要准备什么环境呢?先来看看这张表格,一目了然。

2.1 环境要求

组件版本
JDK1.8+
Spring Boot2.5.x+
TDengine2.6.x+
Ma ven3.6+

2.2 TDengine 安装

接下来,安装 TDengine。这里给出两种常见方式,一个用于 Linux 环境,另一个是 Docker 部署,强烈推荐后者,省心省力。

Linux 环境

wget https://www.taosdata.com/assets-download/taos/TD-server-2.6.0-Linux-x64-2.0.15.0.tar.gz
tar -xzf TD-server-2.6.0-Linux-x64-2.0.15.0.tar.gz
cd TD-server-2.6.0-Linux-x64-2.0.15.0
sudo ./install.sh

Docker 环境(推荐)

docker run -d --name tdengine -p 6030:6030 tdengine/tdengine:2.6.0

验证安装

taos

三、项目集成步骤

现在来看看如何将 TDengine 集成到 Spring Boot 项目中。

3.1 创建 Spring Boot 项目

你可以使用 Spring Initializr 或者手动创建一个 Ma ven 项目,这都不是问题。

3.2 添加 Ma ven 依赖

pom.xml 中添加以下依赖。这里是核心配置,JDBC 驱动和连接池都安排上了。



    4.0.0
    com.example
    tdengine-demo
    1.0.0
    jar
    
        org.springframework.boot
        spring-boot-starter-parent
        2.5.12
    
    
        1.8
        2.0.0
    
    
        
        
            org.springframework.boot
            spring-boot-starter
        
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
        
            org.springframework.boot
            spring-boot-starter-jdbc
        
        
        
            com.taosdata.jdbc
            taos-jdbcdriver
            ${taos.version}
        
        
        
            com.zaxxer
            HikariCP
        
        
        
            cn.hutool
            hutool-all
            5.7.22
        
        
        
            org.projectlombok
            lombok
            true
        
    
    
        
            
                org.springframework.boot
                spring-boot-ma ven-plugin
            
        
    

3.3 配置文件

接下来是 application.yml 配置文件。数据源、连接池、日志级别,一次搞定。

src/main/resources/application.yml

server:
  port: 8080
spring:
  application:
    name: tdengine-demo
# TDengine 数据源配置
taos:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.taosdata.jdbc.TSDBDriver
    url: jdbc:TAOS://localhost:6030/iot
    username: root
    password: taosdata
    # 连接池配置
    minimum-idle: 5
    maximum-pool-size: 20
    connection-timeout: 30000
    idle-timeout: 600000
    max-lifetime: 1800000
# SQL 执行日志 (开发环境开启)
logging:
  level:
    com.taosdata.jdbc: debug
    com.example.taos: trace

四、自定义注解实现

为了让代码更优雅、更可配置,我们引入了一些自定义注解。这就是框架设计的美妙之处——用注解来完成元数据映射,把复杂的事情简单化。

4.1 @STable - 超级表注解

这个注解用来标记实体类与 TDengine 超级表的映射关系。你可以看到,它支持子表名模板,比如 t_${deviceId},动态解析,非常灵活。

src/main/ja va/com/example/taos/annotation/STable.ja va

package com.example.taos.annotation;

import ja va.lang.annotation.ElementType;
import ja va.lang.annotation.Retention;
import ja va.lang.annotation.RetentionPolicy;
import ja va.lang.annotation.Target;

/**
 * 超级表注解
 * 用于标记 TDengine 的超级表和数据表映射关系
 * 
 * 使用示例:
 * @STable(
 *     value = "meters",           // 超级表名
 *     table = "t_${deviceId}",    // 子表名模板
 *     using = true                // 启用自动创建子表
 * )
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface STable {

    /**
     * 超级表表名
     * @return 超级表名称,如果不使用超级表则返回空字符串
     */
    String value() default "";

    /**
     * 数据表表名
     * 支持使用 ${} 占位符动态生成表名
     * 示例:数据表名 = "t_${deviceId}" → 实际表名:t_device001, t_device002
     * @return 数据表名称模板
     */
    String table();

    /**
     * 是否自动创建数据表
     * true: 如果数据表不存在,插入时自动创建
     * false: 数据表必须预先创建好
     * @return 是否启用自动创建
     */
    boolean using() default false;
}

4.2 @IotTableId - 时间戳字段注解

每个时序数据表都少不了一个时间戳字段。这个注解就是用来标记那个字段的,而且强制要求一个实体类只能有一个。

src/main/ja va/com/example/taos/annotation/IotTableId.ja va

package com.example.taos.annotation;

import ja va.lang.annotation.ElementType;
import ja va.lang.annotation.Retention;
import ja va.lang.annotation.RetentionPolicy;
import ja va.lang.annotation.Target;

/**
 * 时间戳字段注解
 * 用于标记 TDengine 表的主键时间字段
 * 
 * 使用要求:
 * - 字段类型必须是 Date、Long 或 Timestamp
 * - 每个实体类必须有且仅有一个 @IotTableId 字段
 * 
 * 使用示例:
 * @IotTableId
 * private Date ts;
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface IotTableId {

    /**
     * 字段名称 (默认使用实体字段名)
     * @return 表字段名
     */
    String value() default "";
}

4.3 @IotField - 普通数据字段注解

数据字段都交给它。它支持自定义字段名、数据库字段类型,以及小数位数的设置,灵活性相当高。

src/main/ja va/com/example/taos/annotation/IotField.ja va

package com.example.taos.annotation;

import ja va.lang.annotation.ElementType;
import ja va.lang.annotation.Retention;
import ja va.lang.annotation.RetentionPolicy;
import ja va.lang.annotation.Target;
import ja va.sql.Types;

/**
 * 普通数据字段注解
 * 用于标记 TDengine 表的数据列
 * 
 * 使用示例:
 * @IotField("voltage")
 * private Double voltage;
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface IotField {

    /**
     * 表字段名 (默认使用实体字段名)
     * @return 字段名
     */
    String value() default "";

    /**
     * 表字段类型 (默认使用实体字段类型)
     * @see ja va.sql.Types
     * @return 字段类型
     */
    int type() default Types.NULL;

    /**
     * 保留小数位数 (针对浮点数)
     * -1 表示不处理
     * @return 小数位数
     */
    int scale() default -1;
}

4.4 @IotTag - 标签字段注解

TDengine 的超级表有一个特色功能就是 Tag。它用来存储设备的维度信息,比如设备ID、位置、类型等,是进行高效聚合查询的关键。

src/main/ja va/com/example/taos/annotation/IotTag.ja va

package com.example.taos.annotation;

import ja va.lang.annotation.ElementType;
import ja va.lang.annotation.Retention;
import ja va.lang.annotation.RetentionPolicy;
import ja va.lang.annotation.Target;
import ja va.sql.Types;

/**
 * 标签字段注解
 * 用于标记 TDengine 超级表的 TAG 列
 * TAG 是 TDengine 的特色,用于设备维度查询
 * 
 * 使用示例:
 * @IotTag("device_id")
 * private String deviceId;
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface IotTag {

    /**
     * 标签名称 (默认使用实体字段名)
     * @return 标签名
     */
    String value() default "";

    /**
     * 标签字段类型 (默认使用实体字段类型)
     * @see ja va.sql.Types
     * @return 字段类型
     */
    int type() default Types.NULL;
}

五、核心工具类实现

好,注解看完了,核心工具类来了。有了这些类,元数据解析、SQL 生成、表名解析,一切都变得井井有条。

5.1 字段元数据类

这个类封装了实体字段和数据库列的映射信息。它负责把 Ja va 反射的 Field 变成一个更易于管理的元数据对象。

src/main/ja va/com/example/taos/sql/FieldMeta.ja va

package com.example.taos.sql;

import ja va.lang.reflect.Field;

/**
 * 字段元数据
 * 封装实体类字段的映射信息
 */
public class FieldMeta {

    /** 字段名 */
    private String fieldName;

    /** 字段类型 (Ja va 类型) */
    private String fieldType;

    /** 数据库字段名 */
    private String columnName;

    /** 数据库字段类型 */
    private int columnType;

    /** 反射字段对象 */
    private Field field;

    /** 小数位数 */
    private int scale = -1;

    public FieldMeta() {
    }

    public FieldMeta(Field field) {
        this.field = field;
        this.fieldName = field.getName();
        this.fieldType = field.getType().getSimpleName();
        this.columnName = field.getName();
        field.setAccessible(true);
    }

    // Getter 和 Setter
    public String getFieldName() {
        return fieldName;
    }

    public void setFieldName(String fieldName) {
        this.fieldName = fieldName;
    }

    public String getFieldType() {
        return fieldType;
    }

    public void setFieldType(String fieldType) {
        this.fieldType = fieldType;
    }

    public String getColumnName() {
        return columnName;
    }

    public void setColumnName(String columnName) {
        this.columnName = columnName;
    }

    public int getColumnType() {
        return columnType;
    }

    public void setColumnType(int columnType) {
        this.columnType = columnType;
    }

    public Field getField() {
        return field;
    }

    public void setField(Field field) {
        this.field = field;
    }

    public int getScale() {
        return scale;
    }

    public void setScale(int scale) {
        this.scale = scale;
    }

    /**
     * 获取字段值
     */
    public Object getFieldValue(Object entity) throws IllegalAccessException {
        return field.get(entity);
    }
}

5.2 表元数据类

这个类封装了完整的表结构映射信息,包括超级表名、子表名模板、字段列表和标签列表。它放了一堆生成 SQL 的方法,很实用。

src/main/ja va/com/example/taos/sql/TaosTableMeta.ja va

package com.example.taos.sql;

import ja va.util.ArrayList;
import ja va.util.List;

/**
 * TDengine 表元数据
 * 封装超级表、数据表、字段的完整映射信息
 */
public class TaosTableMeta {

    /** 超级表名 */
    private String stableName;

    /** 数据表名模板 */
    private String tableNameTemplate;

    /** 是否自动创建数据表 */
    private boolean using;

    /** 时间戳字段 */
    private FieldMeta timeField;

    /** 数据字段列表 */
    private List dataFields = new ArrayList<>();

    /** 标签字段列表 */
    private List tagFields = new ArrayList<>();

    // Getter 和 Setter
    public String getStableName() {
        return stableName;
    }

    public void setStableName(String stableName) {
        this.stableName = stableName;
    }

    public String getTableNameTemplate() {
        return tableNameTemplate;
    }

    public void setTableNameTemplate(String tableNameTemplate) {
        this.tableNameTemplate = tableNameTemplate;
    }

    public boolean isUsing() {
        return using;
    }

    public void setUsing(boolean using) {
        this.using = using;
    }

    public FieldMeta getTimeField() {
        return timeField;
    }

    public void setTimeField(FieldMeta timeField) {
        this.timeField = timeField;
    }

    public List getDataFields() {
        return dataFields;
    }

    public void setDataFields(List dataFields) {
        this.dataFields = dataFields;
    }

    public List getTagFields() {
        return tagFields;
    }

    public void setTagFields(List tagFields) {
        this.tagFields = tagFields;
    }

    /**
     * 生成 INSERT SQL 语句
     */
    public String generateInsertSql() {
        StringBuilder sb = new StringBuilder();
        sb.append("(");
        
        // 时间戳字段
        sb.append(timeField.getColumnName());
        
        // 数据字段
        for (FieldMeta field : dataFields) {
            sb.append(", ").append(field.getColumnName());
        }
        
        sb.append(")");
        return sb.toString();
    }

    /**
     * 生成参数占位符 SQL
     */
    public String generateParamSql() {
        StringBuilder sb = new StringBuilder();
        sb.append("(");
        sb.append("?");
        
        int totalFields = 1 + dataFields.size();
        for (int i = 1; i < totalFields; i++) {
            sb.append(", ?");
        }
        
        sb.append(")");
        return sb.toString();
    }

    /**
     * 生成超级表 USING 子句
     */
    public String generateUsingClause() {
        if (stableName == null || stableName.isEmpty()) {
            return null;
        }

        StringBuilder sb = new StringBuilder();
        sb.append("USING ").append(stableName);
        
        if (!tagFields.isEmpty()) {
            sb.append(" (");
            for (int i = 0; i < tagFields.size(); i++) {
                if (i > 0) sb.append(", ");
                sb.append(tagFields.get(i).getColumnName());
            }
            sb.append(")");
        }

        return sb.toString();
    }

    /**
     * 生成 TAG 值列表
     */
    public String generateTagValues() {
        if (tagFields.isEmpty()) {
            return null;
        }

        StringBuilder sb = new StringBuilder();
        sb.append("(");
        
        for (int i = 0; i < tagFields.size(); i++) {
            if (i > 0) sb.append(", ");
            sb.append("?");
        }
        
        sb.append(")");
        return sb.toString();
    }

    /**
     * 获取总字段数
     */
    public int getTotalFields() {
        return 1 + dataFields.size(); // 时间戳 + 数据字段
    }
}

5.3 SQL 执行上下文

一个轻量级的封装,将 SQL 语句和参数打包在一起,方便传递和执行。

src/main/ja va/com/example/taos/sql/SqlExecContext.ja va

package com.example.taos.sql;

/**
 * SQL 执行上下文
 * 封装要执行的 SQL 语句和参数
 */
public class SqlExecContext {

    /** SQL 语句 */
    private String sql;

    /** 参数值数组 */
    private Object[] params;

    public SqlExecContext(String sql, Object[] params) {
        this.sql = sql;
        this.params = params;
    }

    public String getSql() {
        return sql;
    }

    public void setSql(String sql) {
        this.sql = sql;
    }

    public Object[] getParams() {
        return params;
    }

    public void setParams(Object[] params) {
        this.params = params;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("SQL: ").append(sql).append("n");
        sb.append("Params: ");
        if (params != null) {
            for (Object param : params) {
                sb.append(param).append(", ");
            }
        }
        return sb.toString();
    }
}

5.4 元数据解析器

这才是重头戏。它负责扫描实体类上的注解,解析出结构化的元数据。它还引入了缓存,避免重复解析开销。

src/main/ja va/com/example/taos/sql/TaosMetaExtractor.ja va

package com.example.taos.annotation;
import com.example.taos.sql.FieldMeta;
import com.example.taos.sql.TaosTableMeta;

import ja va.lang.reflect.Field;
import ja va.sql.Date;
import ja va.sql.Types;
import ja va.util.Map;
import ja va.util.concurrent.ConcurrentHashMap;

/**
 * TDengine 元数据解析器
 * 从实体类注解中提取表结构信息
 */
public class TaosMetaExtractor {

    /** 元数据缓存 */
    private static final Map, TaosTableMeta> META_CACHE = new ConcurrentHashMap<>();

    /**
     * 解析实体类的表元数据
     */
    public static TaosTableMeta parse(Class entityClass) {
        return META_CACHE.computeIfAbsent(entityClass, TaosMetaExtractor::doParse);
    }

    /**
     * 清空缓存
     */
    public static void clearCache() {
        META_CACHE.clear();
    }

    private static TaosTableMeta doParse(Class entityClass) {
        STable sTable = entityClass.getAnnotation(STable.class);
        if (sTable == null) {
            throw new IllegalArgumentException(
                "实体类必须使用 @STable 注解:" + entityClass.getName()
            );
        }

        TaosTableMeta meta = new TaosTableMeta();
        meta.setStableName(sTable.value());
        meta.setTableNameTemplate(sTable.table());
        meta.setUsing(sTable.using());

        // 解析字段
        FieldMeta timeField = null;

        for (Field field : entityClass.getDeclaredFields()) {
            field.setAccessible(true);

            if (field.isAnnotationPresent(IotTableId.class)) {
                // 时间戳字段
                if (timeField != null) {
                    throw new IllegalArgumentException(
                        "实体类只能有一个 @IotTableId 字段:" + entityClass.getName()
                    );
                }
                timeField = parseField(field, Types.TIMESTAMP);

                // 验证类型
                Class fieldType = field.getType();
                if (!Date.class.isAssignableFrom(fieldType) 
                    && !Long.class.isAssignableFrom(fieldType) 
                    && field.getType() != long.class) {
                    throw new IllegalArgumentException(
                        "@IotTableId 字段必须是 Date 或 Long 类型:" + field.getName()
                    );
                }

            } else if (field.isAnnotationPresent(IotField.class)) {
                // 普通数据字段
                IotField annotation = field.getAnnotation(IotField.class);
                FieldMeta fieldMeta = parseField(field, annotation.type());
                
                // 自定义字段名
                if (!annotation.value().isEmpty()) {
                    fieldMeta.setColumnName(annotation.value());
                }
                
                // 小数位数
                if (annotation.scale() >= 0) {
                    fieldMeta.setScale(annotation.scale());
                }
                
                meta.getDataFields().add(fieldMeta);

            } else if (field.isAnnotationPresent(IotTag.class)) {
                // 标签字段
                IotTag annotation = field.getAnnotation(IotTag.class);
                FieldMeta fieldMeta = parseField(field, annotation.type());
                
                // 自定义标签名
                if (!annotation.value().isEmpty()) {
                    fieldMeta.setColumnName(annotation.value());
                }
                
                meta.getTagFields().add(fieldMeta);
            }
        }

        if (timeField == null) {
            throw new IllegalArgumentException(
                "实体类必须有且仅有一个 @IotTableId 字段:" + entityClass.getName()
            );
        }

        meta.setTimeField(timeField);
        return meta;
    }

    private static FieldMeta parseField(Field field, int columnType) {
        FieldMeta meta = new FieldMeta(field);
        
        // 根据 Ja va 类型推断数据库类型
        if (columnType == Types.NULL) {
            columnType = inferSqlType(field.getType());
        }
        meta.setColumnType(columnType);
        
        return meta;
    }

    /**
     * 根据 Ja va 类型推断 SQL 类型
     */
    private static int inferSqlType(Class type) {
        if (type == String.class) {
            return Types.VARCHAR;
        } else if (type == Integer.class || type == int.class) {
            return Types.INTEGER;
        } else if (type == Long.class || type == long.class) {
            return Types.BIGINT;
        } else if (type == Double.class || type == double.class) {
            return Types.DOUBLE;
        } else if (type == Float.class || type == float.class) {
            return Types.FLOAT;
        } else if (type == Boolean.class || type == boolean.class) {
            return Types.BOOLEAN;
        } else if (ja va.util.Date.class.isAssignableFrom(type) 
                || ja va.sql.Date.class.isAssignableFrom(type)
                || ja va.sql.Timestamp.class.isAssignableFrom(type)) {
            return Types.TIMESTAMP;
        } else {
            return Types.VARCHAR;
        }
    }
}

5.5 动态表名解析器

当表名里包含 ${fieldName} 这样的占位符时,就需要这个解析器来干活了。它负责从实体对象中提取对应字段的值,替换占位符,生成真实的子表名。

src/main/ja va/com/example/taos/sql/TableNameResolver.ja va

package com.example.taos.sql;

import ja va.lang.reflect.Field;
import ja va.util.regex.Matcher;
import ja va.util.regex.Pattern;

/**
 * 动态表名解析器
 * 解析表名模板中的 ${fieldName} 占位符
 */
public class TableNameResolver {

    private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\$\{([^}]+)\}");

    /**
     * 解析表名
     * 示例:tableTemplate = "t_${deviceId}", entity.deviceId = "device001"
     *      返回:"t_device001"
     */
    public static String resolve(String tableTemplate, Object entity) {
        if (tableTemplate == null || tableTemplate.isEmpty()) {
            throw new IllegalArgumentException("表名模板不能为空");
        }

        Matcher matcher = PLACEHOLDER_PATTERN.matcher(tableTemplate);
        String tableName = tableTemplate;

        while (matcher.find()) {
            String fieldName = matcher.group(1);
            String fieldValue = getFieldValue(entity, fieldName);
            
            if (fieldValue == null) {
                throw new IllegalArgumentException(
                    "解析表名失败,字段 [" + fieldName + "] 的值为 null"
                );
            }

            tableName = tableName.replace("${" + fieldName + "}", fieldValue);
        }

        return tableName;
    }

    /**
     * 获取字段值
     */
    private static String getFieldValue(Object entity, String fieldName) {
        try {
            Field field = entity.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            Object value = field.get(entity);
            return value != null ? value.toString() : null;
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new IllegalArgumentException(
                "无法获取字段值:" + fieldName, e
            );
        }
    }
}

六、数据访问层实现

底层工具类准备就绪,数据访问层就可以直接上手了。

6.1 数据源配置类

Spring Boot 项目,数据源配置是第一步。这个类把配置文件中 taos.datasource 前缀的属性绑定到 HikariCP 连接池。

src/main/ja va/com/example/taos/config/TaosConfig.ja va

package com.example.taos.config;

import com.zaxxer.hikari.HikariDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import ja vax.sql.DataSource;

/**
 * TDengine 数据源配置
 */
@Configuration
public class TaosConfig {

    /**
     * 创建 TDengine 数据源
     */
    @Bean("taosDataSource")
    @ConfigurationProperties(prefix = "taos.datasource")
    public DataSource taosDataSource() {
        return new HikariDataSource();
    }
}

6.2 TDengine 操作接口

定义一个 Repository 接口,先约定好数据访问的方法。

src/main/ja va/com/example/taos/repository/TaosRepository.ja va

package com.example.taos.repository;

import ja va.util.List;

/**
 * TDengine 数据访问接口
 */
public interface TaosRepository {

    /**
     * 插入单条数据
     * @param entity 实体对象
     * @return 影响的行数
     */
    int insert(Object entity);

    /**
     * 批量插入数据
     * @param entities 实体对象列表
     * @return 影响的行数
     */
    int batchInsert(List entities);

    /**
     * 分批批量插入数据
     * @param entities 实体对象列表
     * @param batchSize 每批数量
     * @return 影响的行数
     */
    int batchInsert(List entities, int batchSize);
}

6.3 TDengine 操作实现类

这是具体的数据访问实现。它依赖上面写的工具类,通过元数据解析、动态表名,拼接出正确的 INSERT SQL,然后通过 JDBC 执行。批量插入时还按表名做了分组处理,很周到。

src/main/ja va/com/example/taos/repository/TaosRepositoryImpl.ja va

package com.example.taos.repository;

import com.example.taos.annotation.IotTableId;
import com.example.taos.annotation.STable;
import com.example.taos.sql.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Repository;

import ja vax.sql.DataSource;
import ja va.sql.*;
import ja va.util.*;

/**
 * TDengine 数据访问实现类
 */
@Repository
public class TaosRepositoryImpl implements TaosRepository {

    private static final Logger logger = LoggerFactory.getLogger(TaosRepositoryImpl.class);

    private final DataSource dataSource;

    public TaosRepositoryImpl(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Override
    public int insert(Object entity) {
        try {
            SqlExecContext context = buildInsertContext(entity);
            return executeUpdate(context);
        } catch (Exception e) {
            logger.error("插入数据失败", e);
            throw new RuntimeException("插入数据失败", e);
        }
    }

    @Override
    public int batchInsert(List entities) {
        if (entities == null || entities.isEmpty()) {
            return 0;
        }
        try {
            return executeBatch(entities);
        } catch (Exception e) {
            logger.error("批量插入数据失败", e);
            throw new RuntimeException("批量插入数据失败", e);
        }
    }

    @Override
    public int batchInsert(List entities, int batchSize) {
        if (entities == null || entities.isEmpty()) {
            return 0;
        }

        int totalInserted = 0;
        int totalSize = entities.size();
        int batchCount = (totalSize + batchSize - 1) / batchSize;

        for (int i = 0; i < batchCount; i++) {
            int fromIndex = i * batchSize;
            int toIndex = Math.min(fromIndex + batchSize, totalSize);
            List batch = entities.subList(fromIndex, toIndex);
            totalInserted += batchInsert(batch);
        }

        return totalInserted;
    }

    /**
     * 构建 INSERT SQL 上下文
     */
    private SqlExecContext buildInsertContext(Object entity) throws Exception {
        Class entityClass = entity.getClass();
        
        // 解析元数据
        TaosTableMeta meta = TaosMetaExtractor.parse(entityClass);
        
        // 解析表名 (支持动态表名)
        String tableName = TableNameResolver.resolve(
            meta.getTableNameTemplate(), entity
        );

        // 构建 SQL
        StringBuilder sql = new StringBuilder();
        sql.append("INSERT INTO ").append(tableName);

        // 如果使用超级表自动创建
        if (meta.isUsing() && meta.getStableName() != null && !meta.getStableName().isEmpty()) {
            String usingClause = meta.generateUsingClause();
            if (usingClause != null) {
                sql.append(" ").append(usingClause);
            }
            
            String tagValues = meta.generateTagValues();
            if (tagValues != null) {
                sql.append(" tags ").append(tagValues);
            }
        }

        // 数据字段
        sql.append(" ").append(meta.generateInsertSql());
        sql.append(" VALUES ");
        sql.append(meta.generateParamSql());

        // 构建参数
        List params = new ArrayList<>();
        
        // 时间戳参数
        FieldMeta timeField = meta.getTimeField();
        Object timeValue = timeField.getFieldValue(entity);
        if (timeValue == null) {
            // 自动生成时间戳
            timeValue = new ja va.util.Date();
        }
        params.add(convertTimestamp(timeValue));

        // 数据字段参数
        for (FieldMeta field : meta.getDataFields()) {
            params.add(field.getFieldValue(entity));
        }

        // TAG 字段参数 (如果有 USING 子句)
        if (meta.isUsing() && !meta.getTagFields().isEmpty()) {
            for (FieldMeta tagField : meta.getTagFields()) {
                params.add(tagField.getFieldValue(entity));
            }
        }

        return new SqlExecContext(sql.toString(), params.toArray());
    }

    /**
     * 转换时间戳
     */
    private Object convertTimestamp(Object timeValue) {
        if (timeValue instanceof ja va.util.Date) {
            return new Timestamp(((ja va.util.Date) timeValue).getTime());
        } else if (timeValue instanceof Long) {
            return new Timestamp((Long) timeValue);
        } else {
            return timeValue;
        }
    }

    /**
     * 批量执行
     */
    private int executeBatch(List entities) throws Exception {
        if (entities.isEmpty()) {
            return 0;
        }

        Class entityClass = entities.get(0).getClass();
        TaosTableMeta meta = TaosMetaExtractor.parse(entityClass);

        // 按表名分组 (不同设备的数据表可能不同)
        Map> groupedEntities = new HashMap<>();
        
        for (Object entity : entities) {
            String tableName = TableNameResolver.resolve(
                meta.getTableNameTemplate(), entity
            );
            groupedEntities.computeIfAbsent(tableName, k -> new ArrayList<>()).add(entity);
        }

        // 按表名分别执行批量插入
        int totalInserted = 0;
        
        for (Map.Entry> entry : groupedEntities.entrySet()) {
            String tableName = entry.getKey();
            List tableEntities = entry.getValue();
            
            // 构建批量 SQL
            StringBuilder sql = new StringBuilder();
            sql.append("INSERT INTO ").append(tableName);

            // USING 子句 (只取第一个实体的 TAG 值)
            if (meta.isUsing() && meta.getStableName() != null && !meta.getStableName().isEmpty()) {
                String usingClause = meta.generateUsingClause();
                if (usingClause != null) {
                    sql.append(" ").append(usingClause);
                }
                
                Object firstEntity = tableEntities.get(0);
                String tagValues = buildTagValues(firstEntity, meta);
                if (tagValues != null) {
                    sql.append(" tags ").append(tagValues);
                }
            }

            sql.append(" ").append(meta.generateInsertSql());
            sql.append(" VALUES ");

            // 多行 VALUES
            List allParams = new ArrayList<>();
            for (int i = 0; i < tableEntities.size(); i++) {
                if (i > 0) sql.append(", ");
                sql.append(meta.generateParamSql());
                
                Object entity = tableEntities.get(i);
                List params = buildParams(entity, meta);
                allParams.addAll(params);
            }

            SqlExecContext context = new SqlExecContext(sql.toString(), allParams.toArray());
            totalInserted += executeUpdate(context);
        }

        return totalInserted;
    }

    /**
     * 构建 TAG 值字符串
     */
    private String buildTagValues(Object entity, TaosTableMeta meta) {
        if (meta.getTagFields().isEmpty()) {
            return null;
        }

        StringBuilder sb = new StringBuilder("(");
        for (int i = 0; i < meta.getTagFields().size(); i++) {
            if (i > 0) sb.append(", ");
            try {
                Object value = meta.getTagFields().get(i).getFieldValue(entity);
                if (value instanceof String) {
                    sb.append("'").append(value).append("'");
                } else {
                    sb.append(value);
                }
            } catch (IllegalAccessException e) {
                throw new RuntimeException("获取 TAG 字段值失败", e);
            }
        }
        sb.append(")");
        return sb.toString();
    }

    /**
     * 构建参数列表
     */
    private List buildParams(Object entity, TaosTableMeta meta) throws IllegalAccessException {
        List params = new ArrayList<>();
        
        // 时间戳
        FieldMeta timeField = meta.getTimeField();
        Object timeValue = timeField.getFieldValue(entity);
        if (timeValue == null) {
            timeValue = new ja va.util.Date();
        }
        params.add(convertTimestamp(timeValue));

        // 数据字段
        for (FieldMeta field : meta.getDataFields()) {
            params.add(field.getFieldValue(entity));
        }

        return params;
    }

    /**
     * 执行 SQL 更新
     */
    private int executeUpdate(SqlExecContext context) throws SQLException {
        long startTime = System.currentTimeMillis();
        
        try (Connection conn = dataSource.getConnection();
             PreparedStatement stmt = conn.prepareStatement(context.getSql())) {
            
            // 设置参数
            Object[] params = context.getParams();
            for (int i = 0; i < params.length; i++) {
                stmt.setObject(i + 1, params[i]);
            }

            int rows = stmt.executeUpdate();
            
            if (logger.isTraceEnabled()) {
                logger.trace("TDengine 执行成功 ({}ms): {} 行", 
                    System.currentTimeMillis() - startTime, rows);
            }

            return rows;
        }
    }
}

七、业务服务层

数据访问层搭好了,就可以构建业务服务了。这个 DeviceDataService 提供单条保存、批量保存、模拟数据等功能。它把数据写入包装成异步操作,不阻塞上层调用,适合 IoT 高并发采集场景。

src/main/ja va/com/example/taos/service/DeviceDataService.ja va

package com.example.taos.service;

import com.example.taos.entity.DeviceData;
import com.example.taos.repository.TaosRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import ja va.util.ArrayList;
import ja va.util.Date;
import ja va.util.List;

/**
 * 设备数据服务类
 */
@Service
public class DeviceDataService {

    private static final Logger logger = LoggerFactory.getLogger(DeviceDataService.class);

    @Autowired
    private TaosRepository taosRepository;

    /**
     * 保存单条设备数据 (异步)
     */
    @Async
    public void sa veData(DeviceData data) {
        data.setTs(new Date());
        taosRepository.insert(data);
        logger.debug("保存设备数据:deviceId={}, voltage={}", 
            data.getDeviceId(), data.getVoltage());
    }

    /**
     * 批量保存设备数据 (异步)
     */
    @Async
    public void batchSa ve(List dataList) {
        for (DeviceData data : dataList) {
            data.setTs(new Date());
        }
        int rows = taosRepository.batchInsert(dataList);
        logger.info("批量保存设备数据:{} 条,成功 {} 行", dataList.size(), rows);
    }

    /**
     * 分批保存设备数据 (适合大数据量)
     */
    @Async
    public void batchSa veWithSize(List dataList, int batchSize) {
        for (DeviceData data : dataList) {
            data.setTs(new Date());
        }
        int rows = taosRepository.batchInsert(dataList, batchSize);
        logger.info("分批保存设备数据:{} 条 (每批 {} 条),成功 {} 行", 
            dataList.size(), batchSize, rows);
    }

    /**
     * 模拟设备数据采集
     */
    public DeviceData simulateData(String deviceId, String location, String deviceType) {
        DeviceData data = new DeviceData(deviceId);
        data.setLocation(location);
        data.setDeviceType(deviceType);

        // 模拟传感器数据
        data.setVoltage(220.0 + Math.random() * 10);
        data.setCurrent(5.0 + Math.random() * 2);
        data.setActivePower(1000.0 + Math.random() * 100);
        data.setReactivePower(50.0 + Math.random() * 10);
        data.setPowerFactor(0.95 + Math.random() * 0.05);

        return data;
    }

    /**
     * 批量模拟数据
     */
    public List simulateBatchData(int count, String deviceId, 
                                               String location, String deviceType) {
        List dataList = new ArrayList<>(count);
        for (int i = 0; i < count; i++) {
            dataList.add(simulateData(deviceId, location, deviceType));
        }
        return dataList;
    }
}

八、控制器层

控制器暴露 RESTful 接口。这个设计亮点在于——它支持单条上报、批量上报和模拟生成数据。对于 IoT 平台来说,API 接口的简洁性非常重要,这里的实现就是正解。

src/main/ja va/com/example/taos/controller/DeviceDataController.ja va

package com.example.taos.controller;

import com.example.taos.entity.DeviceData;
import com.example.taos.service.DeviceDataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import ja va.util.HashMap;
import ja va.util.List;
import ja va.util.Map;

/**
 * 设备数据控制器
 */
@RestController
@RequestMapping("/api/device")
@CrossOrigin(origins = "*")
public class DeviceDataController {

    @Autowired
    private DeviceDataService deviceDataService;

    /**
     * 上报单条设备数据
     */
    @PostMapping("/report")
    public Map reportData(@RequestBody DeviceData data) {
        Map result = new HashMap<>();
        try {
            deviceDataService.sa veData(data);
            result.put("code", 200);
            result.put("message", "数据上报成功");
        } catch (Exception e) {
            result.put("code", 500);
            result.put("message", "数据上报失败:" + e.getMessage());
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 批量上报设备数据
     */
    @PostMapping("/batch-report")
    public Map batchReport(@RequestBody List dataList) {
        Map result = new HashMap<>();
        try {
            deviceDataService.batchSa ve(dataList);
            result.put("code", 200);
            result.put("message", "批量上报成功,共 " + dataList.size() + " 条");
        } catch (Exception e) {
            result.put("code", 500);
            result.put("message", "批量上报失败:" + e.getMessage());
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 模拟设备数据生成
     */
    @GetMapping("/simulate")
    public Map simulate(
        @RequestParam(defaultValue = "device001") String deviceId,
        @RequestParam(defaultValue = "10") int count
    ) {
        Map result = new HashMap<>();
        try {
            List dataList = deviceDataService.simulateBatchData(
                count, deviceId, "北京机房", "智能电表"
            );
            deviceDataService.batchSa veWithSize(dataList, 100);
            result.put("code", 200);
            result.put("message", "模拟生成 " + count + " 条数据成功");
            result.put("data", dataList);
        } catch (Exception e) {
            result.put("code", 500);
            result.put("message", "模拟失败:" + e.getMessage());
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 健康检查接口
     */
    @GetMapping("/health")
    public Map health() {
        Map result = new HashMap<>();
        result.put("code", 200);
        result.put("message", "服务正常运行");
        return result;
    }
}

九、实体类定义

实体类 DeviceData 是上面所有注解的集大成者。它映射了超级表 meters,子表名模板 t_${deviceId},并开启了自动创建子表。从字段定义中,也能看出一个智能电表的数据模型该长什么样。

src/main/ja va/com/example/taos/entity/DeviceData.ja va

package com.example.taos.entity;

import com.example.taos.annotation.IotTableId;
import com.example.taos.annotation.STable;
import com.example.taos.annotation.IotField;
import com.example.taos.annotation.IotTag;

import ja va.util.Date;

/**
 * 智能电表数据采集实体
 * 
 * 数据库映射:
 * - 超级表:meters
 * - 子表:t_device001, t_device002, ...
 */
@STable(
    value = "meters",           // 超级表名
    table = "t_${deviceId}",    // 子表名模板 (自动替换 ${deviceId})
    using = true                // 启用自动创建子表
)
public class DeviceData {

    // ========== 时间戳字段 (必填) ==========
    @IotTableId
    private Date ts;

    // ========== 普通数据字段 ==========
    /** 电压 (V) */
    @IotField("voltage")
    private Double voltage;

    /** 电流 (A) */
    @IotField("current")
    private Double current;

    /** 有功功率 (W) */
    @IotField("active_power")
    private Double activePower;

    /** 无功功率 (var) */
    @IotField("reactive_power")
    private Double reactivePower;

    /** 功率因数 */
    @IotField("power_factor")
    private Double powerFactor;

    // ========== 标签字段 (用于设备维度查询) ==========
    /** 设备编号 */
    @IotTag("device_id")
    private String deviceId;

    /** 安装位置 */
    @IotTag("location")
    private String location;

    /** 设备类型 */
    @IotTag("device_type")
    private String deviceType;

    // 默认构造函数
    public DeviceData() {
        this.ts = new Date();
    }

    // 带设备编号的构造函数
    public DeviceData(String deviceId) {
        this();
        this.deviceId = deviceId;
    }

    // Getter 和 Setter
    public Date getTs() {
        return ts;
    }

    public void setTs(Date ts) {
        this.ts = ts;
    }

    public Double getVoltage() {
        return voltage;
    }

    public void setVoltage(Double voltage) {
        this.voltage = voltage;
    }

    public Double getCurrent() {
        return current;
    }

    public void setCurrent(Double current) {
        this.current = current;
    }

    public Double getActivePower() {
        return activePower;
    }

    public void setActivePower(Double activePower) {
        this.activePower = activePower;
    }

    public Double getReactivePower() {
        return reactivePower;
    }

    public void setReactivePower(Double reactivePower) {
        this.reactivePower = reactivePower;
    }

    public Double getPowerFactor() {
        return powerFactor;
    }

    public void setPowerFactor(Double powerFactor) {
        this.powerFactor = powerFactor;
    }

    public String getDeviceId() {
        return deviceId;
    }

    public void setDeviceId(String deviceId) {
        this.deviceId = deviceId;
    }

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }

    public String getDeviceType() {
        return deviceType;
    }

    public void setDeviceType(String deviceType) {
        this.deviceType = deviceType;
    }

    @Override
    public String toString() {
        return "DeviceData{" +
                "ts=" + ts +
                ", deviceId='" + deviceId + ''' +
                ", voltage=" + voltage +
                ", current=" + current +
                ", activePower=" + activePower +
                ", reactivePower=" + reactivePower +
                ", powerFactor=" + powerFactor +
                '}';
    }
}

十、启动类

最后一步,启动类。加上了 @EnableAsync 注解,异步功能就开启了。

src/main/ja va/com/example/taos/TaosDemoApplication.ja va

package com.example.taos;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

/**
 * TDengine Demo 启动类
 */
@SpringBootApplication
@EnableAsync  // 启用异步处理
public class TaosDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(TaosDemoApplication.class, args);
        System.out.println("=========================================");
        System.out.println("   TDengine Demo 启动成功!");
        System.out.println("   API 地址:http://localhost:8080/api/device");
        System.out.println("   健康检查:http://localhost:8080/api/device/health");
        System.out.println("=========================================");
    }
}

十一、初始化数据库

运行项目之前,先把 TDengine 数据库和超级表建好。

11.1 创建数据库和超级表

使用 taos shell 或客户端工具执行下面这段 SQL,就能创建数据库、创建超级表、定义 Tag。

-- 创建数据库
CREATE DATABASE IF NOT EXISTS iot;
-- 使用数据库
USE iot;
-- 创建超级表
CREATE STABLE IF NOT EXISTS meters (
    ts TIMESTAMP,
    voltage DOUBLE,
    current DOUBLE,
    active_power DOUBLE,
    reactive_power DOUBLE,
    power_factor DOUBLE
) TAGS (
    device_id VARCHAR(50),
    location VARCHAR(50),
    device_type VARCHAR(50)
);
-- 查看超级表结构
DESCRIBE meters;
-- 查看数据库
SHOW DATABASES;

11.2 验证连接

建完表后,可以手动查一下,确认连接和表结构都是正常的。

# 进入 taos shell
taos
# 执行查询
USE iot;
SELECT * FROM meters LIMIT 10;

十二、测试验证

一切就绪,我们可以跑起来看看效果了。

12.1 启动项目

mvn spring-boot:run

看到以下输出表示启动成功:

=========================================
   TDengine Demo 启动成功!
   API 地址:http://localhost:8080/api/device
   健康检查:http://localhost:8080/api/device/health
=========================================

12.2 使用 cURL 测试

健康检查

curl http://localhost:8080/api/device/health

单条数据上报

curl -X POST http://localhost:8080/api/device/report 
  -H "Content-Type: application/json" 
  -d '{
    "deviceId": "device001",
    "location": "北京机房",
    "deviceType": "智能电表",
    "voltage": 220.5,
    "current": 10.2,
    "activePower": 2200.5,
    "reactivePower": 100.3,
    "powerFactor": 0.95
  }'

批量数据上报

curl -X POST http://localhost:8080/api/device/batch-report 
  -H "Content-Type: application/json" 
  -d '[
    {
      "deviceId": "device001",
      "location": "北京机房",
      "deviceType": "智能电表",
      "voltage": 220.5,
      "current": 10.2,
      "activePower": 2200.5,
      "reactivePower": 100.3,
      "powerFactor": 0.95
    },
    {
      "deviceId": "device002",
      "location": "上海机房",
      "deviceType": "智能电表",
      "voltage": 219.8,
      "current": 9.8,
      "activePower": 2150.0,
      "reactivePower": 98.5,
      "powerFactor": 0.94
    }
  ]'

模拟数据生成

curl "http://localhost:8080/api/device/simulate?deviceId=device003&count=100"

12.3 在 TDengine 中查询

数据写进去之后,在 taos 里就可以灵活查询了。这也是 TDengine 的强项。

-- 使用数据库
USE iot;
-- 查询所有数据
SELECT * FROM meters;
-- 查询特定设备的数据
SELECT * FROM t_device001;
-- 按设备统计
SELECT device_id, COUNT(*), A VG(voltage), A VG(current) 
FROM meters 
GROUP BY device_id;
-- 时间范围查询
SELECT * FROM meters 
WHERE ts >= '2024-01-01 00:00:00' AND ts <= NOW();
-- 查看所有子表
SHOW TABLES;

十三、性能优化建议

跑起来只是第一步,如何写得快、用得稳才是关键。下面几条建议,都是实战中总结出来的。

13.1 批量写入

千万不要一条一条写,那是性能杀手。推荐的做法是一次写入 1000-5000 条。

// ✅ 推荐:批量写入,每批 1000-5000 条
service.batchSa veWithSize(dataList, 1000);

// ❌ 避免:单条循环写入
for (DeviceData data : dataList) {
    service.sa veData(data);  // 性能差
}

13.2 异步处理

@Async 把数据写入操作放到后台线程执行,主线程不阻塞,用户体验好。

@Service
public class DeviceDataService {
    
    @Async
    public void sa veData(DeviceData data) {
        // 异步写入,不阻塞主线程
        taosRepository.insert(data);
    }
}

配置异步线程池:

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
    
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(1000);
        executor.setThreadNamePrefix("taos-async-");
        executor.initialize();
        return executor;
    }
}

13.3 连接池优化

连接池参数别用默认值。这里给出一个适合高并发场景的配置参考。

taos:
  datasource:
    # 最小连接数
    minimum-idle: 5
    # 最大连接数
    maximum-pool-size: 20
    # 连接超时时间 (ms)
    connection-timeout: 30000
    # 空闲连接超时时间 (ms)
    idle-timeout: 600000
    # 连接最大生命周期 (ms)
    max-lifetime: 1800000

13.4 表设计优化

  1. 合理设计 Tag: 把常用作查询过滤条件的字段设为 Tag,能有效提升查询性能。
  2. 子表数量控制: 建议单超级表下的子表数量不要超过 10 万张,超出后性能会下滑。
  3. 数据保留策略: 根据业务需求配置数据保留时长,老数据自动清理,避免表无限膨胀。
  4. 分区策略: 时序数据库天然支持按时间自动分区,这个要利用好。

十四、常见问题

实践过程中,肯定会遇到一些坑。下面列了几个最常见的,一次性说清楚。

Q1: 连接失败 “Unable to load JNI libraries”

原因: TDengine JDBC 驱动依赖本地 JNI 库

解决方案:

Linux:

# 安装 TDengine 客户端
yum install -y TDengine-client
# 或设置 LD_LIBRARY_PATH
export LD_LIBRARY_PATH=/usr/local/taos/driver:$LD_LIBRARY_PATH

Docker:

# 确保容器已安装 TDengine 客户端
docker exec -it tdengine taos

Q2: 中文乱码

解决方案: 在 JDBC URL 中添加字符集参数

taos:
  datasource:
    url: jdbc:TAOS://localhost:6030/iot?charset=UTF-8

Q3: 批量插入性能低

优化建议:

  1. 增加批次大小(1000-5000 条/批)
  2. 使用异步写入
  3. 增加连接池大小
  4. 检查网络延迟

Q4: 表不存在错误

解决方案:

  1. 确保 @STable(using = true) 已设置
  2. 检查表名模板 ${fieldName} 对应字段是否有值
  3. 手动创建超级表

十五、项目目录结构

最后贴一下项目目录结构,方便你对照参考。

tdengine-demo/
├── src/
│   └── main/
│       ├── ja va/
│       │   └── com/
│       │       └── example/
│       │           └── taos/
│       │               ├── TaosDemoApplication.ja va          # 启动类
│       │               ├── annotation/                        # 自定义注解
│       │               │   ├── STable.ja va
│       │               │   ├── IotTableId.ja va
│       │               │   ├── IotField.ja va
│       │               │   └── IotTag.ja va
│       │               ├── config/                            # 配置类
│       │               │   └── TaosConfig.ja va
│       │               ├── controller/                        # 控制器
│       │               │   └── DeviceDataController.ja va
│       │               ├── entity/                            # 实体类
│       │               │   └── DeviceData.ja va
│       │               ├── repository/                        # 数据访问层
│       │               │   ├── TaosRepository.ja va
│       │               │   └── TaosRepositoryImpl.ja va
│       │               ├── service/                           # 服务层
│       │               │   └── DeviceDataService.ja va
│       │               └── sql/                               # SQL 工具类
│       │                   ├── FieldMeta.ja va
│       │                   ├── TaosTableMeta.ja va
│       │                   ├── SqlExecContext.ja va
│       │                   ├── TaosMetaExtractor.ja va
│       │                   └── TableNameResolver.ja va
│       └── resources/
│           └── application.yml                                # 配置文件
├── pom.xml                                                    # Ma ven 配置
└── README.md                                                  # 项目说明

十六、总结

从零开始,把 Spring Boot 集成 TDengine 的完整流程梳理了一遍。从依赖配置、自定义注解、核心工具类,到数据访问层、业务服务层和控制器,再到数据库初始化、常见问题排查,几乎把整个链路都打通了。

TDengine 作为国产开源时序数据库,在 IoT 场景下的表现确实出色。超级表 + 子表的设计,配合标签查询,轻松应对海量设备数据的采集与聚合。

以上,就是 SpringBoot 集成 TDengine 的完整指南。这篇文章覆盖了从理论到落地的全过程,希望能给你带来一些帮助。

来源:https://www.jb51.net/program/3654109jf.htm

游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。

同类文章
更多
Python+Pytest接口自动化测试方案从零到一实战实现指南

Python+Pytest接口自动化测试方案从零到一实战实现指南

该方案基于Python+Pytest,采用分层结构清晰管理配置、接口、用例与数据。使用Requests库发送HTTP请求,通过YAML文件实现数据驱动与多环境灵活切换,利用Allure生成美观的可视化测试报告,支持统一断言机制,并可对接Jenkins实现持续集成自动化。

时间:2026-06-10 06:55
Python基础教程:列表查找排序与反转的常用方法

Python基础教程:列表查找排序与反转的常用方法

Python列表的查找(index与count用法)、排序(sort与sorted区别及key参数自定义排序)和反转(reverse、reversed及切片)操作详解,涵盖常见错误处理(如值不存在、类型错误)与实用技巧(安全查找、多级排序、频率统计),帮助高效处理列表数据。

时间:2026-06-10 06:55
使用Python扩展Unity编辑器实现自定义工具与工作流

使用Python扩展Unity编辑器实现自定义工具与工作流

为UnityPythonScripting包新增存根生成器、Python编辑器窗口和脚本浏览器。存根将C API转为Python类型信息,支持IDE代码补全。编辑器窗口避免C 编译等待。脚本窗口实现浏览与执行。

时间:2026-06-10 06:55
JVM崩溃FatalError问题排查与解决方案

JVM崩溃FatalError问题排查与解决方案

IntelliJIDEA运行Java服务时,启用定时调试器导致JVM因SIGSEGV崩溃,根本原因在于调试器的native代码与G1垃圾回收机制冲突。关闭“启用定时调试器”选项后,问题彻底解决。

时间:2026-06-10 06:54
基于Map和Bean的策略模式Java实现详解

基于Map和Bean的策略模式Java实现详解

利用Spring容器将策略实现注入到Map中,通过业务标识直接匹配调用,替代传统构造方法或set方法选择策略。该方法减少上下文类维护成本,策略扩展只需在配置中心增加映射,业务侧完全解耦,并利用IoC管理策略生命周期。

时间:2026-06-10 06:54
热门专题
更多
刀塔传奇破解版无限钻石下载大全 刀塔传奇破解版无限钻石下载大全
洛克王国正式正版手游下载安装大全 洛克王国正式正版手游下载安装大全
思美人手游下载专区 思美人手游下载专区
好玩的阿拉德之怒游戏下载合集 好玩的阿拉德之怒游戏下载合集
不思议迷宫手游下载合集 不思议迷宫手游下载合集
百宝袋汉化组游戏最新合集 百宝袋汉化组游戏最新合集
jsk游戏合集30款游戏大全 jsk游戏合集30款游戏大全
宾果消消消原版下载大全 宾果消消消原版下载大全
  • 日榜
  • 周榜
  • 月榜