Tdengine整合MybatisPlus动态数据源

概要

通过MybatisPlus实现Tdengine增删改查功能

简介

官方给出MybatisPlus-demo中所有操作通过mapper实现,添加多个实体时所有实体需要重复编写,本方案通过继承MybatisPlus接口实现增删改查,实现通用接口,无需重复编写

动态数据源配置

spring:
 boot:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    dynamic:
      datasource:
        master:
          url: jdbc:mysql://127.0.0.1:3306/test?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver
        # TDengine 配置信息(驱动程序,jdbc连接)
        tdengine:
          driver-class-name: com.taosdata.jdbc.rs.RestfulDriver
          url: jdbc:TAOS-RS://127.0.0.1:6041
          username: root
          password: 123456

配置文件不在本次重点,按照常规配置即可

实现Model

1.首先继承MP的model类,实现一个我们自己的model

@EqualsAndHashCode(callSuper = true)
@Data
public class BaseTaosModel<T extends Model<T>> extends Model<T> implements TaosBaseInterface {

    private static final long serialVersionUID = 2537647301574172972L;

    @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @ApiModelProperty("时间戳")
    private Timestamp ts;

    @JsonIgnore
    @TableField(exist = false)
    @ApiModelProperty("数据库名")
    private String databaseName;

    @JsonIgnore
    @TableField(exist = false)
    @ApiModelProperty("超级表名")
    private String stableName;

    @JsonIgnore
    @TableField(exist = false)
    @ApiModelProperty("表名")
    private String tableName;

    @JsonIgnore
    @TableField(exist = false)
    @ApiModelProperty("insertValue相关语句")
    private String insertValue;

    @JsonIgnore
    @TableField(exist = false)
    @ApiModelProperty("insertTag相关语句")
    private String insertTag;

    @JsonIgnore
    @TableField(exist = false)
    @ApiModelProperty("建表相关语句")
    private String createValues;

    @JsonIgnore
    @TableField(exist = false)
    @ApiModelProperty("批量新增语句")
    private String batchSql;

    /**
     * 时间戳构造方法
     *
     * @param ts 时间戳
     */
    public void setTs(Timestamp ts) {
        this.ts = ts;
    }

    /**
     * 时间戳构造方法
     *
     * @param ts 时间戳
     */
    public void setTs(Long ts) {
        this.ts = new Timestamp(ts);
    }

    /**
     * 时间戳构造方法
     *
     * @param ts 时间戳
     */
    public void setTs(LocalDateTime ts) {
        this.ts = Timestamp.valueOf(ts);
    }

    /**
     * 获取时间戳
     *
     * @return 时间戳
     */
    public Long getTsLong(){
        if (ObjectUtils.isNotEmpty(ts)) {
            return ts.getTime();
        }
        return null;
    }

    /**
     * 获取时间戳
     *
     * @return 时间戳
     */
    @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    public LocalDateTime getTsLocalDateTime() {
        if (ObjectUtils.isNotEmpty(ts)) {
            return ts.toLocalDateTime();
        }
        return null;
    }

    /**
     * 初始化相关语句方法
     *
     * @param baseTaosModel taos基础实体类
     */
    public void init(BaseTaosModel<?> baseTaosModel) {
        // 如果时间戳为空,默认当前时间
        if(ObjectUtils.isEmpty(this.ts)){
            this.ts = new Timestamp(System.currentTimeMillis());
        }
        // 如果数据库或超级表为空,默认从@TableName获取
        if (StringUtils.isEmpty(this.stableName)) {
            this.databaseName = getDatabaseName(baseTaosModel);
        }
        if (StringUtils.isEmpty(this.stableName)) {
            this.stableName = getSTableName(baseTaosModel);
        }
        // 获取value和tag语句
        this.insertValue = getInsertValue(baseTaosModel);
        this.insertTag = getInsertTag(baseTaosModel);
        this.createValues = getCreateSql();
    }

    /**
     * 重写构造方法,初始化成员变量,减少taos的null值存储
     */
    public BaseTaosModel() {
        setStringFieldsToEmpty(this);
    }

}

在其中规定了一个字段ts,用于默认的主键,同时继承了接口TaosBaseInterface,用以实现反射拼接所有实体类的基础语句

public interface TaosBaseInterface {

    default String getDatabaseName(BaseTaosModel<?> baseTaosModel){
        TableName tableName = baseTaosModel.getClass().getAnnotation(TableName.class);
        if(StringUtils.isNotBlank(tableName.value())){
            // 如果格式为DatabaseName.STableName则返回DatabaseName
            String[] split = tableName.value().split("\\.");
            if(StringUtils.isNotBlank(split[0])){
                return split[0];
            }
        }
        return null;
    }


    default String getSTableName(BaseTaosModel<?> baseTaosModel){
        TableName tableName = baseTaosModel.getClass().getAnnotation(TableName.class);
        if(StringUtils.isNotBlank(tableName.value())){
            // 如果格式为DatabaseName.STableName则返回DatabaseName
            String[] split = tableName.value().split("\\.");
            if(StringUtils.isNotBlank(split[1])){
                return split[1];
            }else {
                return tableName.value();
            }
        }
        return null;
    }


    /**
     * 根据实体类动态创建超级表语句
     * @return 建表属性语句
     */
    @JsonIgnore
    default String getCreateSql(){
        Field[] fields = this.getClass().getDeclaredFields();
        // 获取对应字段列表
        List<Field> valueFields = getTagsFields(fields, false);
        List<Field> tagFields = getTagsFields(fields, true);
        StringJoiner values = new StringJoiner(",", "(", ")");
        StringJoiner tags = new StringJoiner(",", "(", ")");
        // 添加默认时间戳属性
        values.add("ts timestamp");
        // 拼接values
        for (Field field : valueFields) {
            try {
                // 获取字段名和状态名
                String name = field.getName();
                String typeName = field.getType().getSimpleName();
                values.add(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, name)+" "+typeName);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
        // 拼接tags
        for (Field field : tagFields) {
            try {
                // 获取字段名和状态名
                String name = field.getName();
                String typeName = field.getType().getSimpleName();
                tags.add(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, name)+" "+typeName);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
        // 拼接建表属性相关语句,工具类替换优化性能 | 替换string类型为nchar(32) | 替换boolean为bool
        return StringUtils.replaceEach(values + " tags " + tags,
                new String[]{"String", "boolean", "Boolean","Long"},
                new String[]{"nchar(32)", "bool", "bool","bigint unsigned"});
    }

    /**
     * 通过反射拼接所有values
     *
     * @return values 所有taos属性value字符串
     */
    default String getInsertValue(BaseTaosModel<?> baseTaosModel) {
        return getValues(baseTaosModel,true) + " values " + getValues(baseTaosModel,false);
    }

    default String getInsertTag(BaseTaosModel<?> baseTaosModel) {
        return getTags(baseTaosModel,true) + " tags " + getTags(baseTaosModel,false);
    }

    /**
     * 通过反射拼接所有values
     *
     * @param baseTaosModel taos基础类
     * @param isColumn 返回字段还是value值(true:返回字段 | false:返回value)
     * @return values 所有taos属性value字符串
     */
    default String getValues(BaseTaosModel<?> baseTaosModel,boolean isColumn) {
        Field[] fields = this.getClass().getDeclaredFields();
        List<Field> orderedField = getTagsFields(fields, false);
        StringJoiner values = new StringJoiner(",", "(", ")");
        StringJoiner keys = new StringJoiner(",", "(", ")");
        keys.add("ts");
        values.add(Long.toString(baseTaosModel.getTs().getTime()));
        for (Field field : orderedField) {

            boolean annotationPresent = field.isAnnotationPresent(TableField.class);
            if(annotationPresent){
                //有此注解
                TableField annotation = field.getAnnotation(TableField.class);
                boolean exist = annotation.exist();
                if(!exist){
                    //无视其存在,不参与语句拼接
                    continue;
                }
            }


            try {
                // 添加字段名
                String name = field.getName();
                keys.add(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, name));
                // 添加字段值
                field.setAccessible(true);
                Object o = field.get(baseTaosModel);
                List<String> valueList = StringUtil.change2String(o);
                values.add(valueList.get(0));
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
        if(isColumn){
            return keys.toString();
        }
        return values.toString();
    }

    /**
     * 通过反射拼接所有tags
     *
     * @param baseTaosModel taos基础类
     * @param isColumn 返回字段还是tags值(true:返回字段 | false:返回tags)
     * @return values 所有taos属性tags字符串
     */
    default String getTags(BaseTaosModel<?> baseTaosModel,boolean isColumn) {
        Field[] fields = this.getClass().getDeclaredFields();
        List<Field> orderedField = getTagsFields(fields, true);
        StringJoiner values = new StringJoiner(",", "(", ")");
        StringJoiner keys = new StringJoiner(",", "(", ")");
        for (Field field : orderedField) {
            try {
                // 添加字段名
                String name = field.getName();
                keys.add(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, name));
                // 添加字段值
                field.setAccessible(true);
                Object o = field.get(baseTaosModel);
                List<String> valueList = StringUtil.change2String(o);
                values.add(valueList.get(0));
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
        if(isColumn){
            return keys.toString();
        }
        return values.toString();
    }

    /**
     * 获取values或tags对应的字段
     *
     * @param fields 反射获取的Field数组
     * @param isTags 是否时tags
     * @return 排序后Field列表
     */
    default List<Field> getTagsFields(Field[] fields, boolean isTags) {
        // 用来存放所有的属性域
        List<Field> values = new ArrayList<>();
        List<Field> tags = new ArrayList<>();
        // 判断是否是tags字段
        for (Field f : fields) {
            if (f.getAnnotation(Tags.class) != null) {
                tags.add(f);
            } else {
                values.add(f);
            }
        }
        if (isTags) {
            return tags;
        }
        return values;
    }

    default void setStringFieldsToEmpty(Object obj) {
        Field[] fields = this.getClass().getDeclaredFields();
        // tag不能为空字符串
        List<Field> tagsFields = getTagsFields(fields, false);
        for (Field field : tagsFields) {
            if (field.getType() == String.class) {
                try {
                    field.setAccessible(true);
                    field.set(obj, "");
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

接口的getDatabaseName方法用于判断@TableName注解,因为taos有超级表概念,经常要跨库查询数据,所以要使用"库名.表名"查询具体表数据,其余方法反射拼接语句

实现Mapper

1.继承MP的BaseMapper接口

public interface BaseTaosMapper<T extends BaseTaosModel<T>> extends BaseMapper<T> {

    /**
     * 创建默认数据库
     * @param baseTaosModel taos实体
     */
    @Update("create database if not exists ${databaseName}")
    void createDatabase(T baseTaosModel);

    /**
     * 创建具有过期时间的数据库
     * @param baseTaosModel taos基础实体
     * @param duration 过期间隔时间
     * @param keep 保留数据时间
     */
    @Update("create database if not exists ${baseTaosModel.databaseName} duration ${duration} keep ${keep}")
    void createDatabaseDuration(T baseTaosModel,
                                @Param("duration")String duration,
                                @Param("keep")String keep);

    /**
     * 创建超级表
     * @param baseTaosModel taos实体
     */
    @Update("create stable if not exists ${databaseName}.${stableName} ${createValues}")
    void createSTable(T baseTaosModel);

    /**
     * 删除超级表
     * @param baseTaosModel taos基础类
     */
    @Update("drop stable if ${databaseName}.${stableName}")
    void dropSuperTable(T baseTaosModel);

    /**
     * 删除表
     * @param baseTaosModel taos基础类
     */
    @Update("drop table if exists ${databaseName}.${tableName}")
    void dropTable(T baseTaosModel);

    /**
     * 新增数据
     * @param baseTaosModel taos基础类
     * @return 影响行数
     */
    @Insert("insert into ${databaseName}.${tableName} using ${databaseName}.${stableName} ${insertTag} ${insertValue}")
    int insert(T baseTaosModel);

    @Insert("insert into ${baseTaosModel.databaseName}.${baseTaosModel.tableName} using ${baseTaosModel.databaseName}.${baseTaosModel.stableName} ${insertSql}")
    int insertBatch(T baseTaosModel,@Param("insertSql") String insertSql);

    /**
     * 获取taos所有数据库名方法
     * @return 数据库名list
     */
    @Select("show databases")
    List<String> showDatabases();

    /**
     * 获取实体类对应数据库下所有超级表名方法
     * @param baseTaosModel taos实体
     * @return 数据库下所有超级表list
     */
    @Select("show ${databaseName}.stables")
    List<String> showSTables(T baseTaosModel);

    /**
     * 获取实体类对应数据库下所有表名方法
     * @param baseTaosModel taos实体
     * @return 数据库下所有表list
     */
    @Select("show ${databaseName}.tables")
    List<String> showTables(T baseTaosModel);

    /**
     * 切换数据库
     * @param baseTaosModel taos实体
     */
    @Update("use ${databaseName}")
    void useDatabase(T baseTaosModel);

    /**
     * 创建数据流
     * @param streamName 流名称
     * @param streamOptions 流参数
     * @param stbName 超级表名称
     * @param subquery 查询语句
     */
    @Update("create stream if not exists ${streamName} ${streamOptions} INTO ${stbName} AS ${subquery}")
    void createStream(@Param("streamName")String streamName,@Param("streamOptions")String streamOptions,
                      @Param("stbName")String stbName,@Param("subquery")String subquery);
}

该接口中定义了一些taos中的常用方法,通过注解实现,因为满足JDBC,所以只需实现新增等操作即可,也可以自己加入一些通用的方法,例如创建数据流,这样子类就无需多次实现

实现Service和Impl

1.继承MP的IService接口

public interface BaseTaosService<T extends BaseTaosModel<T>> extends IService<T> {

    /**
     * 创建默认数据库
     * 重点:最好手动在taos中创建数据库 | 本方法使用于taos初始化默认创建
     *
     * @param baseTaosModel taos基础实体
     */
    void createDatabase(T baseTaosModel);

    /**
     * 创建具有过期时间的数据库
     * 重点:最好手动在taos中创建数据库 | 本方法使用于taos初始化默认创建
     *
     * @param baseTaosModel taos基础实体
     * @param duration      过期间隔时间
     * @param keep          保留数据时间
     */
    void createDatabaseDuration(T baseTaosModel, String duration, String keep);

    /**
     * 创建超级表
     * 重点:最好手动在taos中创建超级表 | 本方法使用于taos初始化默认创建
     * 重点:如需使用,请了解@TableName规则--
     * --多数据库同结构超级表请手动设置数据库
     *
     * @param baseTaosModel taos实体
     */
    void createSTable(T baseTaosModel);

    /**
     * 删除超级表
     *
     * @param baseTaosModel taos基础类
     */
    void dropSuperTable(T baseTaosModel);

    /**
     * 删除表
     *
     * @param baseTaosModel taos基础类
     */
    void dropTable(T baseTaosModel);

    /**
     * 新增数据
     *
     * @param baseTaosModel taos基础类
     * @return 影响行数
     */
    int insert(T baseTaosModel);

    /**
     * 批量新增方法
     * @param baseTaosModels taos基础实体类列表
     * @param count 批量数据上限 需自己预估 留好容错空间,默认1000行
     * @return 影响行数
     */
    int insertBatch(List<T> baseTaosModels, int count);

    /**
     * 批量新增方法
     * @param baseTaosModels taos基础实体类列表
     */
    int insertBatch(List<T> baseTaosModels);

    /**
     * 获取taos所有数据库名方法
     *
     * @return 数据库名list
     */
    List<String> showDatabases();

    /**
     * 获取实体类对应数据库下所有超级表名方法
     *
     * @param baseTaosModel taos实体
     * @return 数据库下所有超级表list
     */
    List<String> showSTables(T baseTaosModel);

    /**
     * 获取实体类对应数据库下所有表名方法
     *
     * @param baseTaosModel taos实体
     * @return 数据库下所有表list
     */
    List<String> showTables(T baseTaosModel);

    /**
     * 切换数据库
     * 重点 : 当前使用restFul连接,无法切换数据库 使用前确认连接方式
     *
     * @param baseTaosModel taos实体
     */
    void useDatabase(T baseTaosModel);

    /**
     * 创建流式计算方法
     *
     * @param streamName    流名称
     * @param streamOptions 流参数
     * @param stbName       表名
     * @param subquery      查询语句
     */
    void createStream(String streamName, String streamOptions, String stbName, String subquery);

    /**
     * 初始化流式计算
     */
    void initStream();
}

该接口定义taos相关所需基础方法,其中建库建超级表最好手动完成,虽然测试用方法创建没有问题,但是涉及到taos的性能规划,可能有很多问题,最好根据服务器容量计算后手动创建

2.实现Impl类,继承自定义Service

@Slf4j
@DS("tdengine")
public class BaseTaosServiceImpl<M extends BaseTaosMapper<T>, T extends BaseTaosModel<T>> extends ServiceImpl<M, T> implements BaseTaosService<T> {

    @Override
    protected Class<T> currentMapperClass() {
        return (Class<T>) this.getResolvableType().as(BaseTaosServiceImpl.class).getGeneric(0).getType();
    }

    @Override
    protected Class<T> currentModelClass() {
        return (Class<T>) this.getResolvableType().as(BaseTaosServiceImpl.class).getGeneric(1).getType();
    }

    @Override
    public void createDatabase(T baseTaosModel) {
        baseTaosModel.init(baseTaosModel);
        baseMapper.createDatabase(baseTaosModel);
    }

    @Override
    public void createDatabaseDuration(T baseTaosModel, String duration, String keep) {
        baseTaosModel.init(baseTaosModel);
        baseMapper.createDatabaseDuration(baseTaosModel, duration, keep);
    }

    @Override
    public void createSTable(T baseTaosModel) {
        baseTaosModel.init(baseTaosModel);
        baseMapper.createDatabase(baseTaosModel);
        baseMapper.createSTable(baseTaosModel);
    }

    @Override
    public void dropSuperTable(T baseTaosModel) {
        baseTaosModel.init(baseTaosModel);
        baseMapper.dropSuperTable(baseTaosModel);
    }

    @Override
    public void dropTable(T baseTaosModel) {
        baseTaosModel.init(baseTaosModel);
        baseMapper.dropTable(baseTaosModel);
    }

    @Override
    public int insert(T baseTaosModel) {
        baseTaosModel.init(baseTaosModel);
        return baseMapper.insert(baseTaosModel);
    }

    @Override
    public int insertBatch(List<T> list, int count) {
        // 默认值设为1000
        if (count == 0) {
            count = 1000;
        }
        // 根据表名转换map
        Map<String, List<T>> baseModelMap = list.stream()
                .filter(baseTaosModel -> ObjectUtils.isNotEmpty(baseTaosModel.getTableName()))
                .collect(Collectors.groupingBy(BaseTaosModel::getTableName));
        Set<String> strings = baseModelMap.keySet();
        // 不同表遍历新增
        int finalCount = count;
        strings.forEach(tableName -> {
            List<T> baseTaosModelList = baseModelMap.get(tableName);
            if (!baseTaosModelList.isEmpty()) {
                // sql字段标志位
                AtomicInteger i = new AtomicInteger(0);
                // StringJoiner无法清除字符,所以采用StringBuilder拼接空格
                StringBuilder tags = new StringBuilder();
                StringBuilder values = new StringBuilder();

                baseTaosModelList.forEach(baseTaosModel -> {
                    // 如果超过传入次数
                    if (i.get() > finalCount) {
                        String insertSql = tags.append(values).toString();
                        baseTaosModel.init(baseTaosModel);
                        baseTaosModel.setBatchSql(insertSql);
                        baseMapper.insertBatch(baseTaosModel, insertSql);
                        // i重新设施为0 | tags和values置空
                        i.set(0);
                        tags.delete(0, tags.length());
                        values.delete(0, values.length());
                    }
                    // 拼接新增语句
                    if (i.getAndIncrement() == 0) {
                        baseTaosModel.init(baseTaosModel);
                        tags.append(baseTaosModel.getInsertTag()).append(" ");
                        values.append(baseTaosModel.getInsertValue()).append(" ");
                    } else {
                        values.append(baseTaosModel.getValues(baseTaosModel, false)).append(" ");
                    }
                });
                String insertSql = tags.append(values).toString();
                T baseTaosModel = baseTaosModelList.get(0);
                baseTaosModel.init(baseTaosModel);
                baseTaosModel.setBatchSql(insertSql);
                baseMapper.insertBatch(baseTaosModel, insertSql);
            }
        });

        return 0;
    }

    @Override
    public int insertBatch(List<T> list) {
        // 根据表名转换map
        Map<String, List<T>> baseModelMap = list.stream()
                .filter(baseTaosModel -> ObjectUtils.isNotEmpty(baseTaosModel.getTableName()))
                .collect(Collectors.groupingBy(BaseTaosModel::getTableName));
        Set<String> strings = baseModelMap.keySet();
        // 不同表遍历新增
        strings.forEach(tableName -> {
            List<T> baseTaosModelList = baseModelMap.get(tableName);
            if (!baseTaosModelList.isEmpty()) {
                // sql字段标志位
                AtomicInteger i = new AtomicInteger(0);
                // 拼接字段
                StringJoiner tags = new StringJoiner(" ");
                StringJoiner values = new StringJoiner(" ");

                baseTaosModelList.forEach(baseTaosModel -> {
                    // 最大字节长度
                    int maxLength = 1000000;
                    // 预测长度
                    if (values.length() * 3 > maxLength) {
                        String insertSql = tags.merge(values).toString();
                        baseTaosModel.init(baseTaosModel);
                        baseTaosModel.setBatchSql(insertSql);
                        baseMapper.insertBatch(baseTaosModel, insertSql);
                        // i重新设施为0 | tags和values置空
                        i.set(0);
                        tags.setEmptyValue("");
                        values.setEmptyValue("");
                    }
                    // 拼接新增语句
                    if (i.getAndIncrement() == 0) {
                        baseTaosModel.init(baseTaosModel);
                        tags.add(baseTaosModel.getInsertTag());
                        values.add(baseTaosModel.getInsertValue());
                    } else {
                        values.add(baseTaosModel.getValues(baseTaosModel, false));
                    }
                });
                String insertSql = tags.merge(values).toString();
                T baseTaosModel = baseTaosModelList.get(0);
                baseTaosModel.init(baseTaosModel);
                baseTaosModel.setBatchSql(insertSql);
                baseMapper.insertBatch(baseTaosModel, insertSql);
            }
        });

        return 0;
    }

    @Override
    public List<String> showDatabases() {
        return baseMapper.showDatabases();
    }

    @Override
    public List<String> showSTables(T baseTaosModel) {
        return baseMapper.showSTables(baseTaosModel);
    }

    @Override
    public List<String> showTables(T baseTaosModel) {
        return baseMapper.showTables(baseTaosModel);
    }

    @Override
    public void useDatabase(T baseTaosModel) {
        baseMapper.useDatabase(baseTaosModel);
    }

    @Override
    public void createStream(String streamName, String streamOptions, String stbName, String subquery) {
        baseMapper.createStream(streamName, streamOptions, stbName, subquery);
    }

    @Override
    public void initStream() {
        //原始数据数据库名
        String originSource = TDengineDatabaseParamEnums.MONTH_THREE.getDatabaseName() + "." + TDengineConstants.TABLE_NUMERICAL_NAME;
        TDengineDatabaseParamEnums[] values = TDengineDatabaseParamEnums.values();
        for (TDengineDatabaseParamEnums value : values) {
            String databaseName = value.getDatabaseName();
            if (databaseName.equals(TDengineDatabaseParamEnums.MONTH_THREE.getDatabaseName())) {
                continue;
            }
            //原始数据源
            List<String> aggregateTimeList = StringUtil.getAggregateTimeByDatabaseName(databaseName);
            aggregateTimeList.forEach(aggregateTime -> {
                String streamName = TDengineConstants.STREAM_NAME_PREFIX + aggregateTime;
                String stbName = TDengineConstants.STREAM_TABLE_PREFIX + aggregateTime;
                //查询内容
                String subquery = StrUtil.format(TDengineSqlConstants.STREAM_SUBQUERY, originSource, aggregateTime);
                createStream(streamName, null, stbName, subquery);
            });
        }
    }
}

该方法实现了taos的增删改等操作,其中如果要使用自定义方法需要先调用init获取库名表名等字段,然后执行,至此整个Taos的MP操作已经实现完毕
注意,需要重构currentMapperClass和currentModelClass用以返回当前的mapper接口和自定义的model,否则会获取上一级的model,将自定义的实体转为字符

使用示例

实现后整体流程和Mysql使用MybatisPlus基本一致,只是将继承接口换为我们自己实现的

1.创建一个实体类

@EqualsAndHashCode(callSuper = true)
@Data
@TableName(value = "test.test1")
@AllArgsConstructor
@NoArgsConstructor
public class Test extends BaseTaosModel<Test> {

    @ApiModelProperty("int值")
    Integer intValue;

    @ApiModelProperty("String值")
    String  strValue;

    @Tags
    @ApiModelProperty("tag的id")
    String eqpId;
}

这里模拟的是设备的数据采集表,通过采集软件获取数据后存储,tag通过设备id绑定
其中加入@Tags注解后会自动把eqpId转为Tag列,每个子表Tag列不可修改,慎重

2.创建mapper

@DS("tdengine")
public interface TestTaosMapper extends BaseTaosMapper<Test> {
}

注意使用@DS实现动态数据源切换

3.创建service接口

public interface TestTaosService extends BaseTaosService<Test> {
}

4.创建impl实现类

@Service
public class TestTaosServiceImpl extends BaseTaosServiceImpl<TestTaosMapper,Test> implements TestTaosService{
}

5.调用方法

@Slf4j
@Component
public class TestDemo {

    @Resource
    private TestTaosServiceImpl testTaosService;

    @Bean
    public void test(){
        // 创建实体
        Test test = new Test();
        test.setIntValue(123);
        test.setStrValue("123");
        test.setEqpId("112233445566");
        // 设置子表名
        test.setTableName("test1_112233445566");
        testTaosService.insert(test);

        List<Test> list = testTaosService.lambdaQuery()
                .eq(Test::getEqpId, "112233445566")
                .list();
        list.forEach(item->{
            log.info("数据值:{}",item.getIntValue());
            log.info("字符值:{}",item.getStrValue());
        });
    }
}

到这即可成功添加数据,查询数据可以直接使用lambdaQuery或其他Mybatis查询
注意,这里查询是查询的超级表,建议使用3.2.0或更高版本,否则查询速度很慢
批量新增等其他可以自行测试

查询速度优化

文章中的基础查询主要使用于高版本taos中(3.2.0及以上),高版本相较于低版本查询速度有很快优化,3.2.0测试单超级表300万数据通过tag条件查询1秒内查出一个子表所有数据,因此可以直接使用lambdaQuery等方式直接查询,添加条件即可,如果有更高的性能要求可以自己实现实体下mapper语句,优化示例

@Mapper
@DS("tdengine")
public interface ElmStatusDurationMapper extends BaseTaosMapper<StatusDuration> {

    @Select("SELECT * FROM equipment_status.equipment_status_duration WHERE tenant_id = #{tenantId} and ( ts >= #{startTime} and ts <= #{endTime} ) or ( end_time >= #{startTime} and end_time <= #{endTime} ) partition by eqp_id")
    List<StatusDuration> getStatus(String tenantId,Long startTime,Long endTime);
}

这里通过自己给这个实体单独创建一个mapper方法优化速度,orderby会极大影响查询速度,所以用partition by优化,有其余需求可以自己实现,注解或xml都可

总结

整个代码是去年实际项目中用到taos批量新增,所以决定整合MP框架实现的
git地址:https://github.com/Andlzz/Tdengine-demo

  • 29
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
可以通过在MybatisPlus中使用自定义的SqlInjector实现TDengine的集成,具体步骤如下: 1. 配置TDengine的JDBC驱动,可以通过如下方式引入依赖: ```xml <dependency> <groupId>com.taosdata</groupId> <artifactId>jdbc-taos</artifactId> <version>1.10.1</version> </dependency> ``` 2. 创建自定义SqlInjector类,继承DefaultSqlInjector,并实现addSqlMap方法,示例代码如下: ```java public class TdengineSqlInjector extends DefaultSqlInjector { @Override public List<AbstractMethod> getMethodList(Class<?> mapperClass) { List<AbstractMethod> methodList = super.getMethodList(mapperClass); methodList.add(new TdengineInsertBatch()); return methodList; } } ``` 其中TdengineInsertBatch就是自定义的批量插入方法。 3. 在MybatisPlus的配置文件中指定自定义SqlInjector,示例代码如下: ```xml <!-- Mybatis Plus 配置 --> <bean id="mybatisPlusConfig" class="com.baomidou.mybatisplus.core.MybatisConfiguration"> <property name="sqlInjector" ref="tdengineSqlInjector"/> </bean> <!-- 自定义 SqlInjector --> <bean id="tdengineSqlInjector" class="com.example.TdengineSqlInjector"/> ``` 4. 在mapper.xml中编TDengine的SQL语句,例如: ```xml <insert id="batchInsert" parameterType="java.util.List"> insert into test (time, value) values <foreach collection="list" item="item" separator=","> (#{item.time}, #{item.value}) </foreach> </insert> ``` 5. 调用批量插入方法,示例代码如下: ```java List<Test> list = new ArrayList<>(); list.add(new Test("2021-10-10 10:00:00", 1.0)); list.add(new Test("2021-10-10 10:01:00", 2.0)); mapper.batchInsert(list); ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值