SpringBoot集成TDengine指南

作者:技术拾光

时间:2026-06-10

浏览:82

TDengine是高性能时序数据库,支持百万级写入、高压缩比及超级表标签查询。集成SpringBoot需JDK1.8+、Maven,添加taos-jdbcdriver依赖并配置数据源。通过@STable、@IotTableId等注解映射超级表、时间戳与标签,实现时序数据高效管理。

文章目录

一、什么是 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/simulatedeviceId=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 liraries”

原因: 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/iotcharset=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 的完整指南。这篇文章覆盖了从理论到落地的全过程,希望能给你带来一些帮助。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章