springBoot多数据源使用tdengine(3.0.7.1)+MySQL+mybatisPlus+druid连接池

news2024/12/24 2:17:12

一、安装部署

1、我这里使用的 3.0.7.1版本,因为我看3.x版本已经发布了一年了,增加了很多新的功能,而且3.x官方推荐,对于2.x的版本,官网都已经推荐进行升级到3.x,所以考虑到项目以后的发展,决定使用3.x版本

2、下载安装包,我这里是linux安装,虽然在3.x版本已经可以支持window安装,但是为了考虑后面的性能测试,后期的部署,所以这里还是使用linux,这里安装步骤,官方说的已经很清楚了,就不再啰嗦了

在这里插入图片描述
3、安装完成后,会将对应的服务安装到系统服务中。我们直接通过 systemctl start xxx来处理即可,官网文档

在这里插入图片描述

4、启动成功后,我们查看服务状态,确实成功的之后

在这里插入图片描述

5、在我们的当前用户的bin目录下,有很多tdengine的可执行文件

在这里插入图片描述

二、远程链接

1、链接之后,链接的方式很多

2、连接器建立连接的方式,TDengine 提供两种:

  • 通过 taosAdapter 组件提供的 REST API 建立与 taosd 的连接,这种连接方式下文中简称“REST 连接”
  • 通过客户端驱动程序 taosc 直接与服务端程序 taosd 建立连接,这种连接方式下文中简称“原生连接”。

3、这里需要说明的一点就是,我们使用 REST 连接,那么在服务端 taosAdapter 的必须得启动,这个如果你不是docker启动,你得手动启动这个服务,不然你就无法使用 REST 连接

就我目前测试来看,只有docker的是创建完成后,会帮你启动 taosAdapter,其它window和linux是没有的,我们需要手动去启动

4、启动/停止 taosAdapter

在 Linux 系统上 taosAdapter 服务默认由 systemd 管理。使用命令 systemctl start taosadapter 可以启动 taosAdapter 服务。使用命令 systemctl stop taosadapter 可以停止 taosAdapter 服务。

在这里插入图片描述

2.1、使用DBeaver 链接

1、下载安装最新的DBeaver

2、点击左上角建立链接,然后选择 TDengine 即可

在这里插入图片描述

3、填写用户名和密码

注意这里可以看到使用工具链接是时,的URL是 TAOS-RS,说明我们的链接方式是 “REST 连接”,所以服务端 taosAdapter 的必须得启动.

在这里插入图片描述
4、这边的驱动链接版本是 3.2.1

在这里插入图片描述
5、链接成功

mp_test 和 bfh 数据库是我自己创建的

在这里插入图片描述

2.2、使用官网客户端 TDengineGUI 链接

1、tdengine 也有做客户端,用于展示,但是体验不太好

在这里插入图片描述
2、点击下载链接 https://github.com/arielyang/TDengineGUI/tags 下载zip ,解压安装即可

在这里插入图片描述
3、链接,方式参考dbeaver 即可

注意这里可以的URL是 TAOS-RS,说明我们的链接方式是 “REST 连接”,所以服务端 taosAdapter 的必须得启动.

在这里插入图片描述

三、springBoot 使用

1、以下的项目中的链接这里使用的都是 原生连接

通过客户端驱动程序 taosc 直接与服务端程序 taosd 建立连接,这种连接方式下文中简称“原生连接”。

2、你也可以使用 RS方式,但是那个方式效率要比原生的低30%

3、只需要把参数 url: jdbc:TAOS://192.168.172.129:6030/mp_test?charset=UTF-8&locale=en_US.UTF-8&timezone=UTC-8 换成 "jdbc:TAOS-RS://192.168.172.129:6030/mp_test?charset=UTF-8&locale=en_US.UTF-8&timezone=UTC-8";即可

3.1、使用JDBC的方式(prepareStatement)

1、不通过 dynamic-datasource-spring-boot-starter这个来完成多数据源的切换,就只是简单的配置数据源以及数据库连接池,这里不直接集成tdengine 到 mybatisPlus 的原因有两个

  • 集成到 mybatisPlus 时需要多数据源,无论是使用dynamic-datasource的方案,还是自己去写,都需要去处理做逻辑代码,可能后期需要版本的维护就又要考虑这个维护
  • 第二就是无论这个tdengine 如何发展,它一定会支持 jdbc 的方案并给出最新的demo,但是可能其它示例更具会很慢,甚至不会支持 mybatis 和 springBoot的其它版本
  • 效率问题,官网的 JDBC 对于提升效率一节中说到,我们要批量插入,并给出了相关JDBC的示例,但是如果我们使用mybatisPlus ,可以就要取看下源码,mybatisPlus是否有做多余的操作,增加我们的业务工作量.

当然这只是我个人看法,所以,不愿意使用这个方案的,可以直接跳到 3.2节,使用 dynamic-datasource-spring-boot-starter 来通过mybatiPlus 来管理多个数据源

3.1.1、项目配置及其代码

项目很简单,大家看下就明白了

1、整个项目结构

在这里插入图片描述

2、pom依赖

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.14</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
        </dependency>

        <!-- mybatis plus 依赖 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>

        <!-- lombok插件 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- alibaba的fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.83</version>
        </dependency>

        <!-- hutool工具 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.5</version>
        </dependency>

        <!-- TDengine 依赖 -->
        <dependency>
            <groupId>com.taosdata.jdbc</groupId>
            <artifactId>taos-jdbcdriver</artifactId>
            <version>3.2.4</version>
        </dependency>

        <!--Druid  方便控制台查看-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.16</version>
        </dependency>
    </dependencies>

3、配置文件

# 项目端口
server:
  port: 7280
  servlet:
    # 项目路径
    context-path: /tadt
  shutdown: graceful  #开启优雅停机

spring:
  application:
    name: thermal-api-demonstration-tdengine
  lifecycle:
    timeout-per-shutdown-phase: 30s #设置缓冲时间 默认也是30s
  # Mysql配置
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/test01?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&useSSL=true&characterEncoding=UTF-8
    username: root
    password: 123456
    type: com.alibaba.druid.pool.DruidDataSource
    name: mysqlDataSource  # 在druid 内数据源的名称
    # springboot2.0整合了hikari ,据说这是目前性能最好的java数据库连接池,但是 druid 有控制面板方便查看
    # 手动配置数据源
    validation-query: SELECT 1 FROM DUAL  # 连接是否有效的查询语句
    validation-query-timeout: 60000 # 连接是否有效的查询超时时间
    # 建议 连接数 = ((核心数 * 2) + 有效磁盘数)
    initial-size: 40  #初始化时建立物理连接的个数,初始化发生在显示调用 init 方法,或者第一次 getConnection 时
    min-idle: 40  # 最小连接池数量
    max-active: 100  #最大连接池数量
    test-on-borrow: false  #申请连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
    test-on-return: false #归还连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
    time-between-eviction-runs-millis: 60000  # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
    min-evictable-idle-time-millis: 300000  # 配置一个连接在池中最小生存的时间,单位是毫秒
custom:
  datasource:
    driver-class-name: com.taosdata.jdbc.TSDBDriver
    # 这里指定了具体的数据库 需要注意,
    # 如果换成不指定具体数据库名称 jdbc:TAOS://192.168.172.129:6030?charset=UTF-8&locale=en_US.UTF-8&timezone=UTC-8 则在sql中使用必须要指定数据库的名称 dba.table_b
    url: jdbc:TAOS://192.168.172.129:6030/mp_test?charset=UTF-8&locale=en_US.UTF-8&timezone=UTC-8
    username: root
    password: taosdata
    type: com.alibaba.druid.pool.DruidDataSource
    name: tdengineDataSource # 在druid 内数据源的名称
    # springboot2.0整合了hikari ,据说这是目前性能最好的java数据库连接池,但是 druid 有控制面板方便查看
    # 手动配置数据源
    validation-query: select server_status()  # 连接是否有效的查询语句
    validation-query-timeout: 60000 # 连接是否有效的查询超时时间
    # 建议 连接数 = ((核心数 * 2) + 有效磁盘数)
    initial-size: 10  #初始化时建立物理连接的个数,初始化发生在显示调用 init 方法,或者第一次 getConnection 时
    min-idle: 10  # 最小连接池数量
    max-active: 20  #最大连接池数量
    test-on-borrow: false  #申请连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
    test-on-return: false #归还连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
    time-between-eviction-runs-millis: 60000  # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
    min-evictable-idle-time-millis: 300000  # 配置一个连接在池中最小生存的时间,单位是毫秒

  # 热部署
  devtools:
    restart:
      enabled: true
  # jackson 配置
  jackson:
    time-zone: GMT+8
    date-format: yyyy-MM-dd HH:mm:ss
  # 上传文件
  servlet:
    multipart:
      max-file-size: 50MB
      max-request-size: 50MB

logstash:
  host: 192.168.172.228  # logstash部署的服务器IP
  env: dev  # 将日志加载到elk中项目的环境后缀名称-时间

# mybatis配置
mybatis-plus:
  # xml文件路径
  mapper-locations: classpath:mapper/*/*.xml
  # 实体类路径
  type-aliases-package: com.asurplus.*.entity
  configuration:
    # 驼峰转换
    map-underscore-to-camel-case: true
    # 是否开启缓存
    cache-enabled: false
    # 打印sql
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  # 全局配置
  global-config:
    # 数据库字段驼峰下划线转换
    db-column-underline: true
    # id自增类型(数据库id自增)
    id-type: 0

4、CustomDruidDataSource

package cn.jt.thermalapidemonstrationtdengine.config;

import com.alibaba.druid.pool.DruidDataSource;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author GXM
 * @version 1.0.0
 * @Description TODO
 * @createTime 2023年08月01日
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CustomDruidDataSource extends DruidDataSource {

    /**
     * 这里可以添加自定义的数据源属性
     */
    private String customInfo;
}

5、DataSourceConfig

package cn.jt.thermalapidemonstrationtdengine.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

import javax.servlet.Filter;
import javax.sql.DataSource;

/**
 * @author GXM
 * @version 1.0.0
 * @Description TODO
 * @createTime 2023年08月01日
 */
@Component
@Slf4j
public class DataSourceConfig {

    public static final String MYSQL_DATA_SOURCE = "mysqlDataSource";
    public static final String TDENGINE_DATA_SOURCE = "tdengineDataSource";

    /**
     * http://127.0.0.1:7280/tadt/druid
     * 配置Druid的监控视图
     *
     * @return
     */
    @Bean
    public ServletRegistrationBean<StatViewServlet> druidStatViewServlet() {
        ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
        // 配置Druid监控页面的登录用户名和密码
        registrationBean.addInitParameter("loginUsername", "admin");
        registrationBean.addInitParameter("loginPassword", "123456");
        return registrationBean;
    }

    /**
     * 配置Druid的WebStatFilter
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean<Filter> druidWebStatFilter() {
        FilterRegistrationBean<Filter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new WebStatFilter());
        // 配置不拦截的路径
        registrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        registrationBean.addUrlPatterns("/*");
        return registrationBean;
    }

    /**
     * 自动配置的数据源,使用Druid连接池
     *
     * @return
     */
    @Bean(name = MYSQL_DATA_SOURCE)
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() {
        return new DruidDataSource();
    }

    /**
     * 手动配置的数据源,也使用Druid连接池
     *
     * @return
     */
    @Bean(name = TDENGINE_DATA_SOURCE)
    @ConfigurationProperties(prefix = "custom.datasource")
    public DataSource customDataSource() {
        return new CustomDruidDataSource();
    }
}

6、TdEngineController

package cn.jt.thermalapidemonstrationtdengine.controller;

import cn.jt.thermalapidemonstrationtdengine.service.TdEngineService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.sql.Timestamp;

/**
 * @author GXM
 * @version 1.0.0
 * @Description TODO
 * @createTime 2023年07月31日
 */
@Slf4j
@RestController
@RequestMapping("/td")
public class TdEngineController {

    @Autowired
    private TdEngineService tdEngineService;

    @GetMapping("/mockOne")
    public String mockOne() {
        tdEngineService.insertOneByTime(new Timestamp(System.currentTimeMillis()));
        return "ok";
    }

    @GetMapping("/mockMany")
    public String mockMany() {
        tdEngineService.mockMany();
        return "ok";
    }
}

7、TestController

package cn.jt.thermalapidemonstrationtdengine.controller;


import cn.hutool.core.util.RandomUtil;
import cn.jt.thermalapidemonstrationtdengine.entity.A;
import cn.jt.thermalapidemonstrationtdengine.service.AService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author GXM
 * @version 1.0.0
 * @Description TODO
 * @createTime 2023年07月28日
 */
@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {

    @Autowired
    private AService aService;


    @Transactional(rollbackFor = Exception.class)
    @GetMapping("/mockMysql")
    public String mockMysql() {
        A a = new A();
        a.setName(RandomUtil.randomString(5));
        aService.save(a);

        return "ok";
    }


}

8、TestController

package cn.jt.thermalapidemonstrationtdengine.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author GXM
 * @version 1.0.0
 * @Description TODO
 * @createTime 2023年07月28日
 */
@TableName("a")
@AllArgsConstructor
@NoArgsConstructor
@Data
public class A {
    @TableId(type = IdType.AUTO)
    private Integer id;
    private String name;
}

9、AMapper

package cn.jt.thermalapidemonstrationtdengine.mapper;

import cn.jt.thermalapidemonstrationtdengine.entity.A;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

/**
 * @author GXM
 * @version 1.0.0
 * @Description TODO
 * @createTime 2023年07月28日
 */
@Mapper
public interface AMapper extends BaseMapper<A> {
}

10、TdEngineService

package cn.jt.thermalapidemonstrationtdengine.service;

/**
 * @author GXM
 * @version 1.0.0
 * @Description TODO
 * @createTime 2023年07月31日
 */
public interface TdEngineService {

    /**
     * 插入一条数据
     *
     * @param ts 时间
     */
    void insertOneByTime(java.sql.Timestamp ts);

    /**
     * 模拟大量数据
     */
    void mockMany();
}

11、TdEngineServiceImpl

package cn.jt.thermalapidemonstrationtdengine.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.RandomUtil;
import cn.jt.thermalapidemonstrationtdengine.config.DataSourceConfig;
import cn.jt.thermalapidemonstrationtdengine.service.TdEngineService;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.taosdata.jdbc.TSDBPreparedStatement;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import javax.sql.DataSource;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.SQLException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

/**
 * @author GXM
 * @version 1.0.0
 * @Description TODO
 * @createTime 2023年07月31日
 */
@Slf4j
@Service
public class TdEngineServiceImpl implements TdEngineService {
    public static final ExecutorService MESSAGE_LISTENER_HANDLER_EXECUTOR =
            new ThreadPoolExecutor(20, 20,
                    Integer.MAX_VALUE, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(),
                    new ThreadFactoryBuilder().setNameFormat("message-listener-handler-executor-" + "%d")
                            .setUncaughtExceptionHandler((thread, throwable) -> log.error("ThreadPool {} got exception", thread, throwable))
                            .build(), new ThreadPoolExecutor.AbortPolicy());


    @Autowired
    @Qualifier(DataSourceConfig.TDENGINE_DATA_SOURCE)
    private DataSource dataSource;

    @Override
    public void insertOneByTime(java.sql.Timestamp ts) {
        BigDecimal min = BigDecimal.valueOf(0.00);
        BigDecimal max = BigDecimal.valueOf(100.00);
        try (Connection conn = dataSource.getConnection()) {
            String psql = "INSERT INTO bfh.dn_bfh202203450556 VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
            try (TSDBPreparedStatement pstmt = conn.prepareStatement(psql).unwrap(TSDBPreparedStatement.class)) {
                // 如果是使用  parameterIndex 参数的方法,参数下标从1开始...
                pstmt.setTimestamp(1, ts);
                // 一共20个属性,20个通道
                for (int i = 2; i <= 21; i++) {
                    float v = RandomUtil.randomBigDecimal(min, max).floatValue();
                    pstmt.setFloat(i, v);
                }

                int i = pstmt.executeUpdate();
                log.info("插入成功:" + i);
            }
        } catch (SQLException e) {
            log.error("插入error ", e);
        }
    }

    @Override
    public void mockMany() {
        // 三亿条数据
        int total = 300000000;
//        int total = 8 * 10000;
        // 前电脑CPU是8核的(给10个线程数量容易凑成整数)
        int threadCount = 10;
        // 每个线程需要处理的数量
        int perThread = total / threadCount;

        // 所有的任务列表
        List<CompletableFuture<Void>> allTaskList = new ArrayList<>();
        // 起始时间  2022-xx-01 15:30:50.123 ~
        ArrayList<Long> startTimeList = new ArrayList<>(threadCount);
        // 因为数据库设置了过期时间,是3650 即10年,所以插入的数据不能超过这个范围
        for (int i = 0; i < threadCount; i++) {
            LocalDateTime dateTime1 = LocalDateTime.of(2022, i + 1, 1, 15, 30, 50, 123_000_000);
            // 转换为毫秒
            startTimeList.add(dateTime1.toInstant(ZoneOffset.UTC).toEpochMilli());
        }
        String[] device_numbers = {"dn_bfh202203450556", "dn_bfh202307291045"};

        log.info("开始:{}", DateUtil.now());

        for (int i = 0; i < threadCount; i++) {
            int finalI = i;
            CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> {
                // 每个线程每次1000条 数据
                int perSize = 1000;
                int perCount = perThread / perSize;
                int timeCount = 0;
                try (Connection conn = dataSource.getConnection()) {
                    BigDecimal min = BigDecimal.valueOf(0.00);
                    BigDecimal max = BigDecimal.valueOf(100.00);
                    String psql = "INSERT INTO ? VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
                    try (TSDBPreparedStatement pstmt = conn.prepareStatement(psql).unwrap(TSDBPreparedStatement.class)) {
                        for (int j = 1; j <= perCount; j++) {
                            Long curThreadStartTime = startTimeList.get(finalI) + (++timeCount);
                            pstmt.setTableName("bfh." + device_numbers[0]);

                            ArrayList<Long> tsList = new ArrayList<>();
                            for (int k = 0; k < perSize; k++, timeCount++) {
                                tsList.add(curThreadStartTime + k);
                            }
                            pstmt.setTimestamp(0, tsList);

                            // 一共20个属性,20个通道
                            for (int p = 1; p <= 20; p++) {
                                ArrayList<Float> fieldList = new ArrayList<>();
                                for (int p1 = 0; p1 < perSize; p1++) {
                                    fieldList.add(RandomUtil.randomBigDecimal(min, max).floatValue());
                                }
                                pstmt.setFloat(p, fieldList);
                            }
                            // add column
                            pstmt.columnDataAddBatch();
                            // execute column
                            pstmt.columnDataExecuteBatch();
                        }
                    }
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            });
            allTaskList.add(voidCompletableFuture);
        }
        // 等待所有任务完成
        CompletableFuture<Void> allTasks = CompletableFuture.allOf(allTaskList.toArray(new CompletableFuture[allTaskList.size()]));
        // 阻塞等待所有任务完成
        allTasks.join();
        log.info("结束:{}", DateUtil.now());
    }
}

12、AService

package cn.jt.thermalapidemonstrationtdengine.service;

import cn.jt.thermalapidemonstrationtdengine.entity.A;
import com.baomidou.mybatisplus.extension.service.IService;

/**
 * @author GXM
 * @version 1.0.0
 * @Description TODO
 * @createTime 2023年07月28日
 */
public interface AService extends IService<A> {
}

13、AServiceImpl

package cn.jt.thermalapidemonstrationtdengine.service.impl;

import cn.jt.thermalapidemonstrationtdengine.entity.A;
import cn.jt.thermalapidemonstrationtdengine.mapper.AMapper;
import cn.jt.thermalapidemonstrationtdengine.service.AService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;

/**
 * @author GXM
 * @version 1.0.0
 * @Description TODO
 * @createTime 2023年07月28日
 */
@Service
public class AServiceImpl extends ServiceImpl<AMapper, A> implements AService {
}

14、HikariTdEngineConfig.java

空文件

15、ThermalApiDemonstrationTdengineApplication

package cn.jt.thermalapidemonstrationtdengine;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan({"cn.jt.thermalapidemonstrationtdengine.mapper"})
public class ThermalApiDemonstrationTdengineApplication {

    public static void main(String[] args) {
        SpringApplication.run(ThermalApiDemonstrationTdengineApplication.class, args);
    }

}

16、A.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.jt.thermalapidemonstrationtdengine.mapper.AMapper">

</mapper>

17、Druid 数据源展示面板 http://127.0.0.1:7280/tadt/druid/index.html,有两个我们命名的数据源,有各自不同的配置,如果你没有展示,记得,先分别使用 两个数据源操作一下,比如风别请求MockOne 接口,都用到两个数据源,下面就会开始显示了。

在这里插入图片描述

3.1.2、初步感受TDengine的效率(作为粗略参考)

1、官网也给了一个 taosBenchmark,测试工具,大家可以去感受一下

在这里插入图片描述

2、这里要说的是,我们自己写代码感受一下,这边模拟一个设备的温度数据,这个设备一秒10条温度记录,这边模拟它一年的量,三亿条。

3、当然这边这是初略的感受一下,服务器设备(TDengine linux centos7.9)信息如下
在这里插入图片描述

6、生成数据的window电脑

在这里插入图片描述
在这里插入图片描述
7、请求上述接口mockMany,会启动10个线程,每个线程三千万条数据,每次批量插入1000条,这里使用官网的建议

  • 使用批量插入
  • 使用参数绑定

8、这边测试结果就是 开始:2023-08-02 09:02:10 ~ 结束:2023-08-02 09:37:01,其中我还随机操作了MySQL,一共耗时35分钟

  • 注意插入的时间ts字段不能重复,不然会执行数据覆盖的
  • 其次如果数据被大量相同时间戳的数据覆盖过,导致碎片多,数据导出再导入就行了(测试阶段可以直接删除库和表重新建立)。企业版支持在线重组。否则插入会很慢
  • 数据插入乱序的问题,虽然TDengine 会处理,将乱序的数据整理为顺序,但是这样会影响性能
    在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

9、这里补充上数据库,超级表,和子表的结构,如下


 -- 创建一个数据库 保存最近十年的数据
-- KEEP 表示数据文件保存的天数,缺省值为 3650,取值范围 [1, 365000],且必须大于或等于 DURATION 参数值。
-- 数据库会自动删除保存时间超过 KEEP 值的数据。KEEP 可以使用加单位的表示形式,如 KEEP 100h、KEEP 10d 等,支持 m(分钟)、h(小时)和 d(天)三个单位。也可以不写单位,如 KEEP 50,此时默认单位为天
create database bfh KEEP 3650
use bfh
-- 10万个 设备就是 10万个 子表(一个设备一个子表),并且这个超级表下的都是同一种类型的采集量(比如温度)
-- 标签后续可以增加和修改,标签可以用于后续聚合,比如你增加一个 localtion的标签,后续你可以根据地理位置来聚合数据
-- 这里采用多列模型,因为这些通道的温度一定是同时采集的,如果有不同采集频率的可以使用单列模型,就是再建立一个超级表单独处理
-- dn 是 device_number 缩写

CREATE TABLE if not exists bfh_temperature
        (
            ts timestamp,
            temperature1 float,
            temperature2 float,
            temperature3 float,
            temperature4 float,
            temperature5 float,
            temperature6 float,
            temperature7 float,
            temperature8 float,
            temperature9 float,
            temperature10 float,
            temperature11 float,
            temperature12 float,
            temperature13 float,
            temperature14 float,
            temperature15 float,
            temperature16 float,
            temperature17 float,
            temperature18 float,
            temperature19 float,
            temperature20 float
        ) tags ( 
            dn nchar(64)
            )
            
 -- 创建子表的时候,建议将设备唯一标识作为表名称,如设备序列号(就是出厂的时候,硬件那边肯定会有的)
 -- dn 是 device_number 缩写,bfh202307291045是这台设备的型号
create table dn_bfh202307291045 using bfh_temperature tags('bfh202307291045')
create table dn_bfh202203450556 using bfh_temperature tags('bfh202203450556')           
            

--  时间顺序可以乱序,tdengine 会在插入的时候,帮你排好顺序,所以我们可以放心的使用多线程插入
insert into bfh.dn_bfh202307291045(ts,temperature1)  VALUES ('2023-07-29 17:02:45.023',512.20)
--DELETE from bfh.dn_bfh202307291045

select count(*) from bfh.dn_bfh202307291045


--DROP DATABASE bfh;

select count(*) from bfh_temperature;
select count(*) from dn_bfh202203450556;


select * from dn_bfh202203450556 limit  1000000,10;
select * from dn_bfh202203450556 where ts>'2022-01-01 23:47:31.124' limit  1000000,10;

在这里插入图片描述
10、对数据库的数据进行查询操作

  • select count(*) from dn_bfh202203450556; 耗时91ms
    在这里插入图片描述
  • select count(*) from bfh_temperature; 耗时75ms

在这里插入图片描述

  • select * from dn_bfh202203450556 limit 1000000,10; 耗时373ms

在这里插入图片描述

  • select * from dn_bfh202203450556 where ts>‘2022-01-01 23:47:31.124’ limit 1000000,10; 耗时360ms

在这里插入图片描述

11、存盘存储大小,约莫23G

在这里插入图片描述

3.2、使用mybatisPlus 和 dynamic-datasource-spring-boot-starter 来管理

1、项目结构如下,项目也很简单,大家看代码就能明白了

在这里插入图片描述

3.2.1、项目配置及其代码

1、pom.xml

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.14</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
   
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.26</version>
        </dependency>

        <!-- mybatis plus 依赖 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>

        <!--  mybatis plus 动态数据源 多数据源 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>

        <!--Druid  方便控制台查看-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.16</version>
        </dependency>


        <!-- lombok插件 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- alibaba的fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.79</version>
        </dependency>

        <!-- hutool工具 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.5</version>
        </dependency>

        <!-- TDengine 依赖 -->
        <dependency>
            <groupId>com.taosdata.jdbc</groupId>
            <artifactId>taos-jdbcdriver</artifactId>
            <version>3.2.4</version>
        </dependency>
    </dependencies>

2、application.yml

server:
  port: 7012
  shutdown: graceful

spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s
  autoconfigure:
    # DruidDataSourceAutoConfigure会注入一个DataSourceWrapper,其会在原生的spring.datasource下找url,
    # username,password等。动态数据源URL等配置是在dynamic下,因此需要排除,否则会报错。
    # 或者 在启动类上排除 @SpringBootApplication(exclude = DruidDataSourceAutoConfigure.class)
    exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
  # 配置数据源信息
  datasource:
    druid: # 参数说明 https://developer.aliyun.com/article/1157595
      filters: stat,wall  #监控统计(包含慢日志)用的filter:stat 日志用的filter:log4j 防御sql注入的filter:wall (这个必须是全局配置,所有数据源的方式是一样的,你放到具体的数据源下面配置,会发现启动报错)
      web-stat-filter: # 不统计这些请求(这个必须是全局配置,所有数据源的方式是一样的,你放到具体的数据源下面配置,会发现没有这个参数)
        exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
      #设置访问druid监控页面的拦截路径及账号和密码
      stat-view-servlet:
        allow: 127.0.0.1  # 允许那些ip 访问
        login-username: admin
        login-password: 123456
        enabled: true # 开启网页访问  http://127.0.0.1:7012/druid/sql.html
    dynamic:
      # 设置默认的数据源或者数据源组,默认值即为master
      primary: test01
      # 严格匹配数据源,默认false.true未匹配到指定数据源时抛异常,false使用默认数据源
      strict: false
      datasource:
        test01:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://127.0.0.1:3306/test01?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&useSSL=true&characterEncoding=UTF-8
          username: root
          password: 123456
          druid: # 单独配置
            validation-query: SELECT 1 FROM DUAL  # 连接是否有效的查询语句
            validation-query-timeout: 60000
            # 建议 连接数 = ((核心数 * 2) + 有效磁盘数)
            initial-size: 50  #初始化时建立物理连接的个数,初始化发生在显示调用 init 方法,或者第一次 getConnection 时
            min-idle: 50  # 最小连接池数量
            max-active: 100  #最大连接池数量
            test-on-borrow: false  #申请连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
            test-on-return: false #归还连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
            time-between-eviction-runs-millis: 60000  # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
            min-evictable-idle-time-millis: 300000  # 配置一个连接在池中最小生存的时间,单位是毫秒
        test02:
          url: jdbc:mysql://127.0.0.1:3306/test02?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&useSSL=true&characterEncoding=UTF-8
          driver-class-name: com.mysql.cj.jdbc.Driver
          username: root
          password: 123456
          druid: # 单独配置
            validation-query: SELECT 1 FROM DUAL  # 连接是否有效的查询语句
            validation-query-timeout: 60000 # 连接是否有效的查询超时时间
            # 建议 连接数 = ((核心数 * 2) + 有效磁盘数)
            initial-size: 40  #初始化时建立物理连接的个数,初始化发生在显示调用 init 方法,或者第一次 getConnection 时
            min-idle: 40  # 最小连接池数量
            max-active: 100  #最大连接池数量
            test-on-borrow: false  #申请连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
            test-on-return: false #归还连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
            time-between-eviction-runs-millis: 60000  # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
            min-evictable-idle-time-millis: 300000  # 配置一个连接在池中最小生存的时间,单位是毫秒
        thermalDemonstration: # 配置 tdengine 的数据源
          driver-class-name: com.taosdata.jdbc.TSDBDriver
          # 这里指定了具体的数据库 需要注意,
          # 如果换成不指定具体数据库名称 jdbc:TAOS://192.168.172.129:6030?charset=UTF-8&locale=en_US.UTF-8&timezone=UTC-8 则在sql中使用必须要指定数据库的名称 dba.table_b
          url: jdbc:TAOS://192.168.172.129:6030/mp_test?charset=UTF-8&locale=en_US.UTF-8&timezone=UTC-8
          username: root
          password: taosdata
          druid: # 单独配置
            validation-query: select server_status()  # 连接是否有效的查询语句
            validation-query-timeout: 60000 # 连接是否有效的查询超时时间
            # 建议 连接数 = ((核心数 * 2) + 有效磁盘数)
            initial-size: 10  #初始化时建立物理连接的个数,初始化发生在显示调用 init 方法,或者第一次 getConnection 时
            min-idle: 10  # 最小连接池数量
            max-active: 10  #最大连接池数量
            test-on-borrow: false  #申请连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
            test-on-return: false #归还连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
            time-between-eviction-runs-millis: 60000  # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
            min-evictable-idle-time-millis: 300000  # 配置一个连接在池中最小生存的时间,单位是毫秒

# mybatis配置
mybatis-plus:
  # xml文件路径
  mapper-locations: classpath:mapper/*/*.xml
  # 实体类路径
  type-aliases-package: cn.gxm.springboottdengine.entity.*
  configuration:
    # 驼峰转换
    map-underscore-to-camel-case: true
    # 是否开启缓存
    cache-enabled: false
    # 打印sql
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  # 全局配置
  global-config:
    # 数据库字段驼峰下划线转换
    db-column-underline: true
    # id自增类型(数据库id自增)
    id-type: 0

3、TDengineTemperatureController

package cn.gxm.springboottdengine.controller;

import cn.gxm.springboottdengine.entity.thermaldemonstration.Temperature;
import cn.gxm.springboottdengine.mapper.thermaldemonstration.TemperatureMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.sql.Timestamp;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

/**
 * @author GXM
 * @version 1.0.0
 * @Description TODO
 * @createTime 2023年07月28日
 */
@RestController
@RequestMapping("/td/temperature")
public class TDengineTemperatureController {

    private static Random random = new Random(System.currentTimeMillis());
    private static String[] locations = {"北京", "上海", "深圳", "广州", "杭州"};


    @Autowired
    private TemperatureMapper mapper;

    @GetMapping("before")
    public String before() {
        mapper.dropSuperTable();
        // create table temperature
        mapper.createSuperTable();
        // create table t_X using temperature
        for (int i = 0; i < 10; i++) {
            mapper.createTable("t" + i, locations[random.nextInt(locations.length)], i);
        }
        // insert into table
        int affectRows = 0;
        // insert 10 tables
        for (int i = 0; i < 10; i++) {
            // each table insert 5 rows
            for (int j = 0; j < 5; j++) {
                Temperature one = new Temperature();
                one.setTs(new Timestamp(1605024000000L));
                one.setTemperature(random.nextFloat() * 50);
                one.setLocation("望京");
                one.setTbIndex(i);
                affectRows += mapper.insertOne(one);
            }
        }
//        Assert.assertEquals(50, affectRows);
        return "影响数量 : " + affectRows;
    }

    @GetMapping("after")
    public String after() {
        mapper.dropSuperTable();
        return "删除超级表";
    }

    /***
     * test SelectList
     * **/
    @GetMapping("testSelectList")
    public List<Temperature> testSelectList() {
        List<Temperature> temperatureList = mapper.selectList(null);
//        temperatureList.forEach(System.out::println);
        return temperatureList;
    }

    /***
     * test InsertOne which is a custom metheod
     * ***/
    @GetMapping("testInsert")
    public String testInsert() {
        Temperature one = new Temperature();
        one.setTs(new Timestamp(1605025000000L));
        one.setTemperature(random.nextFloat() * 50);
        one.setLocation("望京");
        int affectRows = mapper.insertOne(one);
//        Assert.assertEquals(1, affectRows);
        return "插入行数:" + affectRows;
    }

    /***
     * test  select By map
     * ***/
    @GetMapping("testSelectByMap")
    public List<Temperature> testSelectByMap() {
        Map<String, Object> map = new HashMap<>();
        map.put("location", "北京");
        List<Temperature> temperatures = mapper.selectByMap(map);
//        Assert.assertTrue(temperatures.size() > 1);
        return temperatures;
    }

    /***
     * test selectObjs
     * **/
    @GetMapping("testSelectObjs")
    public List<Object> testSelectObjs() {
        List<Object> ts = mapper.selectObjs(null);
//        System.out.println(ts);
        return ts;
    }

    /**
     * test selectC ount
     **/
    @GetMapping("testSelectCount")
    public long testSelectCount() {
        long count = mapper.selectCount(null);
//        Assert.assertEquals(10, count);
        return count;
    }

    /****
     * 分页
     */
    @GetMapping("testSelectPage")
    public IPage<Temperature> testSelectPage() {
        IPage page = new Page(1, 2);
        IPage<Temperature> temperatureIPage = mapper.selectPage(page, null);
        System.out.println("total : " + temperatureIPage.getTotal());
        System.out.println("pages : " + temperatureIPage.getPages());
        for (Temperature temperature : temperatureIPage.getRecords()) {
            System.out.println(temperature);
        }
        return temperatureIPage;
    }

}

4、TDengineWeatherController

package cn.gxm.springboottdengine.controller;

import cn.gxm.springboottdengine.entity.thermaldemonstration.Weather;
import cn.gxm.springboottdengine.mapper.thermaldemonstration.WeatherMapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.sql.Timestamp;
import java.util.List;
import java.util.Random;


@RestController
@RequestMapping("/td/weather")
public class TDengineWeatherController {

    private static Random random = new Random(System.currentTimeMillis());

    @Autowired
    private WeatherMapper mapper;

    @GetMapping("createTable")
    public void createTable() {
        mapper.dropTable();
        mapper.createTable();
        Weather one = new Weather();
        one.setTs(new Timestamp(1605024000000L));
        one.setTemperature(12.22f);
        one.setLocation("望京");
        one.setHumidity(100);
        mapper.insertOne(one);
    }

    @GetMapping("testSelectList")
    public List<Weather> testSelectList() {
        List<Weather> weathers = mapper.selectList(null);
//        weathers.forEach(System.out::println);
        return weathers;
    }

    @GetMapping("testInsert")
    public int testInsert() {
        Weather one = new Weather();
        one.setTs(new Timestamp(1605024000000L));
        one.setTemperature(random.nextFloat() * 50);
        one.setHumidity(random.nextInt(100));
        one.setLocation("望京");
        int affectRows = mapper.insert(one);
//        Assert.assertEquals(1, affectRows);
        return affectRows;
    }

//    @Test
    @GetMapping("testSelectOne")
    public Weather testSelectOne() {
        QueryWrapper<Weather> wrapper = new QueryWrapper<>();
        wrapper.eq("location", "望京");
        Weather one = mapper.selectOne(wrapper);
//        System.out.println(one);
//        Assert.assertEquals(12.22f, one.getTemperature(), 0.00f);
//        Assert.assertEquals("望京", one.getLocation());
        return one;
    }

    // @Test
    // public void testSelectByMap() {
    //     Map<String, Object> map = new HashMap<>();
    //     map.put("location", "beijing");
    //     List<Weather> weathers = mapper.selectByMap(map);
    //     Assert.assertEquals(1, weathers.size());
    // }

    @GetMapping("testSelectObjs")
    public List<Object> testSelectObjs() {
        List<Object> ts = mapper.selectObjs(null);
//        System.out.println(ts);
        return ts;
    }

    @GetMapping("testSelectCount")
    public long testSelectCount() {
        long count = mapper.selectCount(null);
//        Assert.assertEquals(5, count);
//        System.out.println(count);
        return count;
    }

    @GetMapping("testSelectPage")
    public IPage<Weather> testSelectPage() {
        IPage page = new Page(1, 2);
        IPage<Weather> weatherIPage = mapper.selectPage(page, null);
//        System.out.println("total : " + weatherIPage.getTotal());
//        System.out.println("pages : " + weatherIPage.getPages());
//        for (Weather weather : weatherIPage.getRecords()) {
//            System.out.println(weather);
//        }
        return weatherIPage;
    }

}

5、TestController

package cn.gxm.springboottdengine.controller;

import cn.gxm.springboottdengine.entity.test01.A;
import cn.gxm.springboottdengine.entity.test02.B;
import cn.gxm.springboottdengine.service.test01.AService;
import cn.gxm.springboottdengine.service.test02.BService;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author GXM
 * @version 1.0.0
 * @Description TODO
 * @createTime 2023年07月28日
 */
@RestController
@RequestMapping("/test")
public class TestController {

    @Autowired
    private AService aService;
    @Autowired
    private BService bService;

    @GetMapping("/mockMysql")
//    @Transactional(rollbackFor = Exception.class) 得用 @DSTransactional
    public String mockMysql() {
        A a = new A();
        a.setName(RandomUtil.randomString(5));
        aService.save(a);

        B b = new B();
        b.setAge(RandomUtil.randomInt(1, 200));
        bService.save(b);
        return "ok";
    }

    @GetMapping("/aTrans")
    @DSTransactional
    public String aTrans() {
        A a = new A();
        a.setName(RandomUtil.randomString(5));
        aService.save(a);

        int i = 1 / 0;
        A a2 = new A();
        a2.setName(RandomUtil.randomString(5));
        aService.save(a2);
        return "ok";
    }

    @GetMapping("/bTrans")
    @DSTransactional
    public String bTrans() {
        B b = new B();
        b.setAge(RandomUtil.randomInt(1, 200));
        bService.save(b);

        int i = 1 / 0;
        B b2 = new B();
        b2.setAge(RandomUtil.randomInt(1, 200));
        bService.save(b2);
        return "ok";
    }
}

6、A

package cn.gxm.springboottdengine.entity.test01;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author GXM
 * @version 1.0.0
 * @Description TODO
 * @createTime 2023年07月28日
 */
@TableName("a")
@AllArgsConstructor
@NoArgsConstructor
@Data
public class A {
    @TableId(type = IdType.AUTO)
    private Integer id;
    private String name;
}

7、B

package cn.gxm.springboottdengine.entity.test02;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author GXM
 * @version 1.0.0
 * @Description TODO
 * @createTime 2023年07月28日
 */
@TableName("b")
@AllArgsConstructor
@NoArgsConstructor
@Data
public class B {

    @TableId(type = IdType.AUTO)
    private Integer id;
    private Integer age;
}

8、Temperature

package cn.gxm.springboottdengine.entity.thermaldemonstration;

import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;

import java.sql.Timestamp;

@Data
public class Temperature {

    private Timestamp ts;
    private float temperature;
    private String location;
    @TableField("tbindex")
    private int tbIndex;

}

9、Weather

package cn.gxm.springboottdengine.entity.thermaldemonstration;

import lombok.Data;

import java.sql.Timestamp;

@Data
public class Weather {

    private Timestamp ts;
    private float temperature;
    private int humidity;
    private String location;

}

10、AMapper

package cn.gxm.springboottdengine.mapper.test01;

import cn.gxm.springboottdengine.entity.test01.A;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

/**
 * @author GXM
 * @version 1.0.0
 * @Description TODO
 * @createTime 2023年07月28日
 */
@Mapper
@DS("test01")
public interface AMapper extends BaseMapper<A> {
}

11、BMapper

package cn.gxm.springboottdengine.mapper.test02;

import cn.gxm.springboottdengine.entity.test02.B;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

/**
 * @author GXM
 * @version 1.0.0
 * @Description TODO
 * @createTime 2023年07月28日
 */
@Mapper
@DS("test02")
public interface BMapper extends BaseMapper<B> {
}

12、TemperatureMapper

package cn.gxm.springboottdengine.mapper.thermaldemonstration;

import cn.gxm.springboottdengine.entity.thermaldemonstration.Temperature;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;

@Mapper
@DS("thermalDemonstration")
public interface TemperatureMapper extends BaseMapper<Temperature> {

    // 演示放到 xml 中调用
//    @Update("CREATE TABLE if not exists temperature(ts timestamp, temperature float) tags(location nchar(64), tbIndex int)")
    int createSuperTable();

    @Update("create table #{tbName} using temperature tags( #{location}, #{tbindex})")
    int createTable(@Param("tbName") String tbName, @Param("location") String location, @Param("tbindex") int tbindex);

    @Update("drop table if exists temperature")
    void dropSuperTable();

    @Insert("insert into t${tbIndex}(ts, temperature) values(#{ts}, #{temperature})")
    int insertOne(Temperature one);

}

13、WeatherMapper

package cn.gxm.springboottdengine.mapper.thermaldemonstration;

import cn.gxm.springboottdengine.entity.thermaldemonstration.Weather;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Update;

@Mapper
@DS("thermalDemonstration")
public interface WeatherMapper extends BaseMapper<Weather> {

    // 演示放到 xml 中调用
//    @Update("CREATE TABLE if not exists weather(ts timestamp, temperature float, humidity int, location nchar(100))")
    int createTable();

    @Insert("insert into weather (ts, temperature, humidity, location) values(#{ts}, #{temperature}, #{humidity}, #{location})")
    int insertOne(Weather one);

    @Update("drop table if exists weather")
    void dropTable();
}

14、AServiceImpl

package cn.gxm.springboottdengine.service.test01.impl;

import cn.gxm.springboottdengine.entity.test01.A;
import cn.gxm.springboottdengine.mapper.test01.AMapper;
import cn.gxm.springboottdengine.service.test01.AService;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;

/**
 * @author GXM
 * @version 1.0.0
 * @Description TODO
 * @createTime 2023年07月28日
 */
@Service
public class AServiceImpl extends ServiceImpl<AMapper, A> implements AService {
}

15、AService

package cn.gxm.springboottdengine.service.test01;

import cn.gxm.springboottdengine.entity.test01.A;
import com.baomidou.mybatisplus.extension.service.IService;

/**
 * @author GXM
 * @version 1.0.0
 * @Description TODO
 * @createTime 2023年07月28日
 */
public interface AService extends IService<A> {
}

16、BServiceImpl

package cn.gxm.springboottdengine.service.test02.impl;

import cn.gxm.springboottdengine.entity.test02.B;
import cn.gxm.springboottdengine.mapper.test02.BMapper;
import cn.gxm.springboottdengine.service.test02.BService;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;

/**
 * @author GXM
 * @version 1.0.0
 * @Description TODO
 * @createTime 2023年07月28日
 */
@Service
public class BServiceImpl extends ServiceImpl<BMapper, B> implements BService {
}

17、

package cn.gxm.springboottdengine.service.test02;

import cn.gxm.springboottdengine.entity.test02.B;
import com.baomidou.mybatisplus.extension.service.IService;

/**
 * @author GXM
 * @version 1.0.0
 * @Description TODO
 * @createTime 2023年07月28日
 */
public interface BService extends IService<B> {
}

18、SpringbootTdengineDynamicDataSourceApplication

package cn.gxm.springboottdengine;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@MapperScan({"cn.gxm.springboottdengine.mapper"})
public class SpringbootTdengineDynamicDataSourceApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootTdengineDynamicDataSourceApplication.class, args);
    }

}

19、A.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.gxm.springboottdengine.mapper.test01.AMapper">

</mapper>

20、B.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.gxm.springboottdengine.mapper.test02.BMapper">

</mapper>

21、TemperatureMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.gxm.springboottdengine.mapper.thermaldemonstration.TemperatureMapper">

    <update id="createSuperTable">
        CREATE TABLE if not exists temperature
        (
            ts
            timestamp,
            temperature
            float
        ) tags
        (
            location nchar
        (
            64
        ), tbIndex int)
    </update>
</mapper>

22、WeatherMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.gxm.springboottdengine.mapper.thermaldemonstration.WeatherMapper">

    <update id="createTable">
        CREATE TABLE if not exists weather
        (
            ts
            timestamp,
            temperature
            float,
            humidity
            int,
            location
            nchar
        (
            100
        ))
    </update>
</mapper>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/825182.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

EMC VxRail E560 vusb0端口不断down和up问题

客户的一台VxRail E560报vusb0网卡故障&#xff0c;如下所示&#xff1a; 该端口是VxRail ESXi与iDRAC内部通信的网口&#xff0c;并没有连接外部网络。 在vobd.log日志中&#xff0c;可以看到vusb0不断在down和up的信息&#xff1a; 由于节点在2023年4月份进行了重装&am…

每日一题8.2 2536

2536. 子矩阵元素加 1 给你一个正整数 n &#xff0c;表示最初有一个 n x n 、下标从 0 开始的整数矩阵 mat &#xff0c;矩阵中填满了 0 。 另给你一个二维整数数组 query 。针对每个查询 query[i] [row1i, col1i, row2i, col2i] &#xff0c;请你执行下述操作&#xff1a;…

超出背包限制--潜水员

本题和之前的二维背包差不多&#xff0c;但是它可以做到超出体积的限制&#xff0c;本质上我们的状态转移方程其实和之前的差不多&#xff0c;f[i][j]min(f[i-1][j],f[i-1][j-v]w),超出的体积应该如何去计算&#xff0c;我们可以利用负数去计算&#xff0c;也就是i-v1<0||j-…

如何在烟草行业运用IPD?

从当前的世界烟草行业来看&#xff0c;烟草经济的发展十分迅速&#xff0c;中国是烟草生产与消费第一大国&#xff0c;每年由我国生产与出售的烟草远销世界各地。与此同时&#xff0c;中国烟草行业的集中度越来越高&#xff0c;企业的数量与规模稳步上升&#xff0c;行业迈向规…

Flink - sink算子

水善利万物而不争&#xff0c;处众人之所恶&#xff0c;故几于道&#x1f4a6; 文章目录 1. Kafka_Sink 2. Kafka_Sink - 自定义序列化器 3. Redis_Sink_String 4. Redis_Sink_list 5. Redis_Sink_set 6. Redis_Sink_hash 7. 有界流数据写入到ES 8. 无界流数据写入到ES 9. 自定…

nodejs环境部署

1.前言 前端的打包必须要使用nodejs环境 2.部署 下载安装包&#xff0c;在下面的路径中找到适合自己项目合适的版本在下载&#xff0c;可以问一下前端开发人员 路径&#xff1a;Index of /dist/ 我这里是下载了14.21.3版本的nodejs环境,上传到需要部署的主机中 mkdir /opt…

制砖机系统比例控制阀放大器

制砖机系统是一种生产砖块的机器设备系统。该系统由多个部分组成&#xff0c;包括压力系统、模具和振动系统、烘干和烧制系统等。压力系统是制砖机的主要组成部分之一&#xff0c;它通过压力将原料压缩成一定形状和尺寸的块状&#xff0c;然后经过烘干和烧制等步骤&#xff0c;…

【嵌入式系统开发实训】学生实验报告

一、实验内容 1、过程考核60分&#xff1a; &#xff08;1&#xff09;顺序点亮3个LED灯&#xff08;分数&#xff1a;10分&#xff09;&#xff1b; &#xff08;2&#xff09;按键顺序点亮3个LED灯&#xff08;分数&#xff1a;10分&#xff09;&#xff1b; &#xff08;3&a…

恒运资本:炒股知识有用吗?

炒股是指通过购买和出售股票来赚取差价的一种出资行为。在现代社会&#xff0c;炒股已经成为许多人重视的话题。然而&#xff0c;有些人以为炒股常识是非常有用的&#xff0c;而另一些人则以为炒股常识并不有用。那么&#xff0c;炒股常识终究有多大的用途呢&#xff1f;本文将…

【NLP概念源和流】 06-编码器-解码器模型(6/20 部分)

一、说明 在机器翻译等任务中,我们必须从一系列输入词映射到一系列输出词。读者必须注意,这与“序列标记”不同,在“序列标记”中,该任务是将序列中的每个单词映射到预定义的类,如词性或命名实体任务。 作者生成 在上面的

实现本地上传、FTP上传、阿里云OSS上传三者合一处理

1、选项模式【Options】的处理 文件上传处理应该由程序进行配置&#xff0c;决定使用那种方式&#xff0c;那么这里面我们为了弹性化处理&#xff0c; 在文件上传模块中采用选项模式【Options】处理常规上传和FTP文件上传的配置参数信息。 微软引入选项模式,它是用于配置框架…

宇凡微2.4g遥控船开发方案,采用合封芯片

2.4GHz遥控船的开发方案是一个有趣且具有挑战性的项目。这样的遥控船可以通过无线2.4GHz频率进行远程控制&#xff0c;让用户在池塘或湖泊上畅游。以下是一个简要的2.4GHz遥控船开发方案&#xff1a; 基本构想如下 mcu驱动两个小电机&#xff0c;小电机上安装两个螺旋桨&#…

三、JVM-如何判断对象已死问题

内存模型以及如何判定对象已死问题 体验与验证 2.4.5.1 使用visualvm visualgc插件下载链接 &#xff1a;https://visualvm.github.io/pluginscenters.html 选择对应JDK版本链接—>Tools—>Visual GC 若上述链接找不到合适的&#xff0c;大家也可以自己在网上下载对应…

聊聊虚拟定位工具新宠儿:AnyGo的原理与识别

市面上已经有很多基于位置服务的应用场景&#xff0c;如运动品类应用基于地理位置生成运动轨迹&#xff0c;企业办公应用基于定位信息进行打卡&#xff0c;游戏品类应用基于位置信息开发区域排名&#xff0c;电商品类应用基于位置发放区域性优惠券等等。在黑灰产嗅探到背后的商…

自然语言处理学习笔记(二)————语料库与开源工具

目录 1.语料库 2.语料库建设 &#xff08;1&#xff09;规范制定 &#xff08;2&#xff09;人员培训 &#xff08;3&#xff09;人工标注 3.中文处理中的常见语料库 &#xff08;1&#xff09;中文分词语料库 &#xff08;2&#xff09;词性标注语料库 &#xff08;3…

Elasticsearch:语义搜索 - Semantic Search in python

当 OpenAI 于 2022 年 11 月发布 ChatGPT 时&#xff0c;引发了人们对人工智能和机器学习的新一波兴趣。 尽管必要的技术创新已经出现了近十年&#xff0c;而且基本原理的历史甚至更早&#xff0c;但这种巨大的转变引发了各种发展的“寒武纪大爆炸”&#xff0c;特别是在大型语…

电容笔和触控笔的区别是什么?好用的苹果平替电容笔

如今&#xff0c;随着无纸化学习以及办公的发展&#xff0c;电容笔更是倍受关注。但是&#xff0c;很多人都对于电容笔和触控笔之间的区别都存在着疑惑。其实&#xff0c;这两种产品很好区分&#xff0c;第一种是电容笔&#xff0c;它是适用于我们最常用的电容屏&#xff0c;例…

视频两侧有黑边怎么处理?教你裁切视频黑边方法

现在的大多数电视是16:9的宽屏&#xff0c;而大多数视频都是4:3的标清或是16:9的高清。当你看一个标清或高清视频时&#xff0c;如果它的比例与你的电视屏幕比例不同&#xff0c;视频两侧就会出现黑边。这些黑边会对视频的质量或观看体验产生影响&#xff0c;那么怎么处理呢&am…

双系统安装后开机时没有GRUB界面,直接进入windows系统

电脑配置&#xff1a;512固态1T机械&#xff0c;安装了win10Ubuntu22.04双系统&#xff0c;ubuntu安装在机械硬盘上。安装完成后没有出现GRUB启动管理供选择进入哪一个系统&#xff0c;直接进入windows系统。 解决办法&#xff1a; 进入Bios&#xff08;惠普电脑的快捷键是F1…

Python入门指南:从零开始学习Python编程

文章目录 前言安装Python变量以及数据类型总结 前言 Python是一种简单而又强大的编程语言&#xff0c;它在全球范围内广受欢迎&#xff0c;适用于各种应用场景&#xff0c;包括Web开发、数据分析、人工智能和科学计算等。本篇博客将为初学者提供一份Python入门指南&#xff0c;…