springBoot多数据源使用tdengine(3.0.7.1)+MySQL+mybatisPlus+druid连接池
一、安装部署
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: 7280servlet:# 项目路径context-path: /tadtshutdown: graceful #开启优雅停机spring:application:name: thermal-api-demonstration-tdenginelifecycle:timeout-per-shutdown-phase: 30s #设置缓冲时间 默认也是30s# Mysql配置datasource:druid:# 接下来(one,two)其实就都是自定义配置了,springBoot是识别不了的(当然你也可以另起其它行,到其它位置),我们需要将这些配置映射到对应的类上,springBoot不会帮我们做one:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/test01?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&useSSL=true&characterEncoding=UTF-8username: rootpassword: 123456type: com.alibaba.druid.pool.DruidDataSourcename: 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检测连接是否有效,开启会降低性能,默认为truetest-on-return: false #归还连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为truetime-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位是毫秒two: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_burl: jdbc:TAOS://192.168.172.129:6030/mp_test?charset=UTF-8&locale=en_US.UTF-8&timezone=UTC-8username: rootpassword: taosdatatype: com.alibaba.druid.pool.DruidDataSourcename: 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检测连接是否有效,开启会降低性能,默认为truetest-on-return: false #归还连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为truetime-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位是毫秒# 热部署devtools:restart:enabled: true# jackson 配置jackson:time-zone: GMT+8date-format: yyyy-MM-dd HH:mm:ss# 上传文件servlet:multipart:max-file-size: 50MBmax-request-size: 50MBlogstash:host: 192.168.172.228 # logstash部署的服务器IPenv: dev # 将日志加载到elk中项目的环境后缀名称-时间# mybatis配置
mybatis-plus:# xml文件路径mapper-locations: classpath:mapper/*/*.xml# 实体类路径type-aliases-package: com.asurplus.*.entityconfiguration:# 驼峰转换map-underscore-to-camel-case: true# 是否开启缓存cache-enabled: false# 打印sqllog-impl: org.apache.ibatis.logging.stdout.StdOutImpl# 全局配置global-config:# 数据库字段驼峰下划线转换db-column-underline: true# id自增类型(数据库id自增)id-type: 0
4、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*/@Beanpublic ServletRegistrationBean<StatViewServlet> druidStatViewServlet() {ServletRegistrationBean<StatViewServlet> registrationBean =new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");// 配置Druid监控页面的登录用户名和密码registrationBean.addInitParameter("loginUsername", "admin");registrationBean.addInitParameter("loginPassword", "123456");// 设置 IP 白名单,允许访问的 IP,多个 IP 用逗号分隔registrationBean.addInitParameter("allow", "127.0.0.1");// 设置 IP 黑名单,拒绝访问的 IP,多个 IP 用逗号分隔(当 IP 在黑名单中同时又在白名单中时,优先于白名单)// servletRegistrationBean.addInitParameter("deny", "192.168.1.100");// 是否能够重置数据registrationBean.addInitParameter("resetEnable", "false");return registrationBean;}/*** 配置Druid的WebStatFilter** @return*/@Beanpublic FilterRegistrationBean<Filter> druidWebStatFilter() {FilterRegistrationBean<Filter> registrationBean = new FilterRegistrationBean<>();registrationBean.setFilter(new WebStatFilter());// 添加过滤规则registrationBean.addUrlPatterns("/*");// 配置不拦截的路径registrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");return registrationBean;}/*** 自动配置的数据源,使用Druid连接池* druid 会管理这数据源** @return*/@Bean(name = MYSQL_DATA_SOURCE)@Primary@ConfigurationProperties(prefix = "spring.datasource.druid.one")public DataSource dataSource() {return new DruidDataSource();}/*** 手动配置的数据源,也使用Druid连接池* druid 会管理这数据源* <p>* 这里不直接集成tdengine 到 mybatisPlus 的原因有两个* 1、集成到 mybatisPlus 时需要多数据源,无论是使用dynamic-datasource的方案,还是自己去写,都需要去处理做逻辑代码,可能后期需要版本的维护就又要考虑这个维护* 2、就是无论这个tdengine 如何发展,它一定会支持 jdbc 的方案,但是可能不会支持 mybatis和 springBoot,* 3、效率问题** @return*/@Bean(name = TDENGINE_DATA_SOURCE)@ConfigurationProperties(prefix = "spring.datasource.druid.two")public DataSource customDataSource() {return new DruidDataSource();}
}
5、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 {@Autowiredprivate TdEngineService tdEngineService;@GetMapping("/mockOne")public String mockOne() {tdEngineService.insertOneByTime(new Timestamp(System.currentTimeMillis()));return "ok";}@GetMapping("/mockMany")public String mockMany() {tdEngineService.mockMany();return "ok";}
}
6、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 {@Autowiredprivate AService aService;@GetMapping("/mockMysql")public String mockMysql() {A a = new A();a.setName(RandomUtil.randomString(5));aService.save(a);return "ok";}@Transactional(rollbackFor = Exception.class)@GetMapping("/mockMysql2")public String mockMysql2() {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";}}
7、A
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;
}
8、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> {
}
9、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();
}
10、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;@Overridepublic 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);}}@Overridepublic 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 columnpstmt.columnDataAddBatch();// execute columnpstmt.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());}
}
11、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> {
}
12、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 {
}
13、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);}}
14、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>
15、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 STABLE 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_bfh202307291045select 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

12、CPU 和内存使用


3.1.3、题外说明
1、因为我们给Mysql的数据源增加了 @Primary 注解,所以mybatisPlus 默认管理的是这个
2、如果后期还需要配置另外多个数据源,可以按照现在的方式去配置Three,four,等等数据源,然后再配置jdbcTemplate,再给jdbcTemplate配置事务,我们后面可以使用jdbcTemplate来操作数据库就行啦
- 在 Spring Boot 中,JdbcTemplate 是 Spring 提供的用于执行 SQL 操作的简单 JDBC 工具。如果你在应用程序中使用了多数据源,并且希望为每个数据源分别配置 JdbcTemplate,可以像下面这样实现:
- 在配置类中创建多个 JdbcTemplate Bean,分别关联到各自的数据源。
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;import javax.sql.DataSource;@Configuration
public class JdbcTemplateConfig {@Beanpublic JdbcTemplate jdbcTemplateOne(@Qualifier("dataSourceOne") DataSource dataSourceOne) {return new JdbcTemplate(dataSourceOne);}@Beanpublic JdbcTemplate jdbcTemplateTwo(@Qualifier("dataSourceTwo") DataSource dataSourceTwo) {return new JdbcTemplate(dataSourceTwo);}// 添加其他数据源的 JdbcTemplate Bean,根据需要继续添加
}
- 在需要使用 JdbcTemplate 的 Service 或 Repository 类中,使用 @Autowired 注解注入对应的 JdbcTemplate
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;@Repository
public class MyRepository {private final JdbcTemplate jdbcTemplateOne;private final JdbcTemplate jdbcTemplateTwo;@Autowiredpublic MyRepository(@Qualifier("jdbcTemplateOne") JdbcTemplate jdbcTemplateOne,@Qualifier("jdbcTemplateTwo") JdbcTemplate jdbcTemplateTwo) {this.jdbcTemplateOne = jdbcTemplateOne;this.jdbcTemplateTwo = jdbcTemplateTwo;}// 在这里可以使用 jdbcTemplateOne 和 jdbcTemplateTwo 分别对不同数据源进行数据库操作// ...
}
- 在 Spring Boot 中,JdbcTemplate 默认是不支持事务的。如果你需要在使用 JdbcTemplate 的方法中启用事务支持,你可以使用 Spring 的事务管理机制,即声明式事务。 声明式事务允许你使用注解来标记需要进行事务管理的方法,Spring 将会自动为这些方法添加事务处理。在方法执行过程中,如果发生异常,事务会被回滚;如果方法执行成功,事务会被提交。
- 以下是在 JdbcTemplate 方法中启用事务支持的步骤:配置事务管理器:首先,你需要在配置类中配置事务管理器,以便在应用程序中启用事务支持。
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;import javax.sql.DataSource;@Configuration
@EnableTransactionManagement
public class TransactionConfig {@Beanpublic PlatformTransactionManager transactionManager(@Qualifier("dynamicDataSource") DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}
}
- 在上面的代码中,我们创建了一个名为 transactionManager 的事务管理器 Bean,并将 dynamicDataSource 数据源传递给它。dynamicDataSource 是我们前面配置的动态数据源,它包含了多个数据源。通过在配置类上添加 @EnableTransactionManagement 注解,我们启用了 Spring 的事务管理功能。
- 在需要进行事务管理的 Service 或 Repository 类中,使用 @Transactional 注解标记需要启用事务的方法。
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;@Service
public class MyService {private final JdbcTemplate jdbcTemplate;public MyService(JdbcTemplate jdbcTemplate) {this.jdbcTemplate = jdbcTemplate;}@Transactionalpublic void someTransactionalMethod() {// 在这个方法中执行数据库操作,该方法会启用事务// 如果方法执行过程中出现异常,事务会回滚// 如果方法执行成功,事务会提交}
}
-
通过在 someTransactionalMethod() 方法上添加 @Transactional 注解,我们标记该方法需要进行事务管理。这样,在调用该方法时,Spring 将会自动为该方法添加事务支持,确保数据库操作的原子性和一致性。
-
注意,为了使 @Transactional 注解生效,必须确保该注解是由 Spring 托管的 Bean 方法上使用。如果你在同一个类中的非 public 方法上使用该注解,Spring 将无法应用事务代理。
-
通过上述配置,可以在使用 JdbcTemplate 的方法中启用事务支持,并确保数据库操作的事务安全性。
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: 7012shutdown: gracefulspring:lifecycle:timeout-per-shutdown-phase: 30sautoconfigure:# 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/1157595filters: 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: adminlogin-password: 123456enabled: true # 开启网页访问 http://127.0.0.1:7012/druid/sql.htmldynamic:# 设置默认的数据源或者数据源组,默认值即为masterprimary: test01# 严格匹配数据源,默认false.true未匹配到指定数据源时抛异常,false使用默认数据源strict: falsedatasource:test01:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/test01?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&useSSL=true&characterEncoding=UTF-8username: rootpassword: 123456druid: # 单独配置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检测连接是否有效,开启会降低性能,默认为truetest-on-return: false #归还连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为truetime-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-8driver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: 123456druid: # 单独配置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检测连接是否有效,开启会降低性能,默认为truetest-on-return: false #归还连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为truetime-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_burl: jdbc:TAOS://192.168.172.129:6030/mp_test?charset=UTF-8&locale=en_US.UTF-8&timezone=UTC-8username: rootpassword: taosdatadruid: # 单独配置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检测连接是否有效,开启会降低性能,默认为truetest-on-return: false #归还连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为truetime-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# 打印sqllog-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 = {"北京", "上海", "深圳", "广州", "杭州"};@Autowiredprivate TemperatureMapper mapper;@GetMapping("before")public String before() {mapper.dropSuperTable();// create table temperaturemapper.createSuperTable();// create table t_X using temperaturefor (int i = 0; i < 10; i++) {mapper.createTable("t" + i, locations[random.nextInt(locations.length)], i);}// insert into tableint affectRows = 0;// insert 10 tablesfor (int i = 0; i < 10; i++) {// each table insert 5 rowsfor (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());@Autowiredprivate 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 {@Autowiredprivate AService aService;@Autowiredprivate BService bService;@GetMapping("/mockMysql")
// @Transactional(rollbackFor = Exception.class) 得用 @DSTransactionalpublic 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")@DSTransactionalpublic 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")@DSTransactionalpublic 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(tstimestamp,temperaturefloat) 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(tstimestamp,temperaturefloat,humidityint,locationnchar(100))</update>
</mapper>相关文章:
springBoot多数据源使用tdengine(3.0.7.1)+MySQL+mybatisPlus+druid连接池
一、安装部署 1、我这里使用的 3.0.7.1版本,因为我看3.x版本已经发布了一年了,增加了很多新的功能,而且3.x官方推荐,对于2.x的版本,官网都已经推荐进行升级到3.x,所以考虑到项目以后的发展,决定…...
剑指Offer 05.替换空格
剑指Offer 05.替换空格 目录 剑指Offer 05.替换空格05.替换空格题目代码(容易想到的)利用库函数的方法题解(时间复杂度更低)面试:为什么java中String类型是不可变的 05.替换空格 题目 官网题目地址 代码(…...
ChatGPT的功能与特点
随着人工智能技术的不断发展,ChatGPT作为OpenAI公司开发的基于GPT-3.5架构的大型语言模型,正引领着智能交互的新纪元。ChatGPT的功能与特点使其能够在多个领域展现出惊人的能力,本文将深入探讨ChatGPT的功能与特点,以及它在人工智…...
Vue2.0基础
1、概述 Vue(读音/vju/,类似于view)是一套用于构建用户界面的渐进式框架,发布于2014年2月。与其它大型框架不同的是,Vue被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层(也就是可以理解为HTMLCSSJS)ÿ…...
rust 如何定义[u8]数组?
在Rust中,有多种方式可以定义 [u8] 数组。以下是一些常见的方式: 使用数组字面量初始化数组: let array: [u8; 5] [1, 2, 3, 4, 5];使用 vec! 宏创建可变长度的数组: let mut vec: Vec<u8> vec![1, 2, 3, 4, 5];使用 v…...
关于Hive的使用技巧
前言 Hive是一个基于Hadoop的数据仓库基础架构,它提供了一种类SQL的查询语言,称为HiveQL,用于分析和处理大规模的结构化数据。 Hive的主要特点包括: 可扩展性:Hive可以处理大规模的数据,支持高性能的并行…...
【C++】BSTree 模拟笔记
文章目录 概念插入和删除非递归实现中的问题递归中的引用简化相关OJ复习直达 概念 由下面二叉搜索树的性质可以知道,中序遍历它便可以得到一个升序序列,查找效率高,小于往左找,大于往右走。最多查找高度次,走到到空&am…...
5分钟快手入门laravel邮件通知
第一步: 生成一个邮件发送对象 php artisan make:mail TestMail 第二步: 编辑.env 添加/修改(没有的key则添加) MAIL_DRIVERsmtp MAIL_HOSTsmtp.163.com (这里用163邮箱) MAIL_PORT25 (163邮箱…...
iOS——Block two
Block 的实质究竟是什么呢?类型?变量?还是什么黑科技? Blocks 是 带有局部变量的匿名函数 Blocks 由 OC 转 C 源码方法 在项目中添加 blocks.m 文件,并写好 block 的相关代码。打开「终端」,执行 cd XX…...
Ubuntu出现内部错误解决办法
使用的Ubuntu版本是18.04,使用的时候弹出对话框说出现了内部错误,好奇是哪里出现了错误,查找了一下解决的办法,记录一下。 参考解决方案:ubantu出现了内部错误 一旦程序崩溃过一次,就会生成一个.crash文件…...
2023年中职组“网络安全”赛项吉安市竞赛任务书
2023年中职组“网络安全”赛项 吉安市竞赛任务书 一、竞赛时间 总计:360分钟 竞赛阶段 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 A模块 A-1 登录安全加固 180分钟 200分 A-2 本地安全策略配置 A-3 流量完整性保护 A-4 事件监控 A-5 服务加固…...
ELK日志分析系统介绍及搭建(超详细)
目录 一、ELK日志分析系统简介 二、Elasticsearch介绍 2.1Elasticsearch概述 三、Logstash介绍 四、Kibana介绍 五、ELK工作原理 六、部署ELK日志分析系统 6.1ELK Elasticsearch 集群部署(在Node1、Node2节点上操作) 6.2部署 Elasticsearch 软件 …...
docker 资源限制
目录 1、CPU使用率 2、CPU共享比例 3、CPU周期限制 4、CPU核心限制 5、CPU 配额控制参数的混合案例 6、内存限制 7、Block IO 的限制 8、限制bps 和iops docker资源限制 Docker容器技术底层是通过Cgroup(Control Group 控制组)实现容器对物理资…...
HCIP 交换综合实验--企业三层架构
题目 1、内网IP地址使用172.16.0.0/26分配 2、SW1和SW2之间互为备份 3、VRRP/STP/VLAN/Eth-trunk均使用 4、所有PC均通过DHCP获取IP地址 5、ISP只能配置IP地址 6、所有电脑可以正常访问ISP路由器环回 实验步骤 第一步、规划IP地址 R1-R2:100.1.1.0/24 R2-LSW1…...
微服务的基础使用
微服务 Maven的依赖冲突解决方案: 路径最短原则 配置优先原则 破坏规则则使用排除 SpringBoot场景启动器starter的开发流程 c3p0-spring-boot-starter自定义场景启动器 test-c3p0调用自定义场景启动器 SpringBoot自动装配 SpringBoot应用启动原理 nacos服务治…...
opencv-29 Otsu 处理(图像分割)
Otsu 处理 Otsu 处理是一种用于图像分割的方法,旨在自动找到一个阈值,将图像分成两个类别:前景和背景。这种方法最初由日本学者大津展之(Nobuyuki Otsu)在 1979 年提出 在 Otsu 处理中,我们通过最小化类别内…...
网络中通过IP地址查找位置
display ip routing-table 查看路由表 display vlan 查看vlan 信息 display stp brief 查看生成树信息 display mac-address 查看mac 地址表 display arp 查看arp表 SW1 SW2...
MyBatis的动态SQL语句
文章目录 前言LocalDate数据库代码po 包 ifwhere 标签 查trim 标签 增set 标签 改foreach 标签 删 前言 提示:这里可以添加本文要记录的大概内容: 查询条件是动态的 MyBatis的动态SQL语句是指在运行时根据不同条件选择不同的SQL语句执行。 这些条件可…...
交互式AI技术与模型部署:bert-base-chinese模型交互式问答界面设置
使用Gradio实现Question Answering交互式问答界面,首先你需要有一个已经训练好的Question Answering模型,这里你提到要使用bert-base-chinese模型。 Gradio支持PyTorch和TensorFlow模型,所以你需要将bert-base-chinese模型转换成PyTorch或Te…...
Edge浏览器安装vue devtools
1. 下载地址 GitHub - vuejs/devtools: ⚙️ Browser devtools extension for debugging Vue.js applications. 2. 下载后的压缩包解压并打开文件夹,右键选择:git bush here 3. 安装依赖 npm install 4. 成功安装依赖后打包 npm run build...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...
oracle与MySQL数据库之间数据同步的技术要点
Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异,它们的数据同步要求既要保持数据的准确性和一致性,又要处理好性能问题。以下是一些主要的技术要点: 数据结构差异 数据类型差异ÿ…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...
【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)
本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …...
Kafka入门-生产者
生产者 生产者发送流程: 延迟时间为0ms时,也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于:异步发送不需要等待结果,同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...
深度学习水论文:mamba+图像增强
🧀当前视觉领域对高效长序列建模需求激增,对Mamba图像增强这方向的研究自然也逐渐火热。原因在于其高效长程建模,以及动态计算优势,在图像质量提升和细节恢复方面有难以替代的作用。 🧀因此短时间内,就有不…...
【JavaSE】多线程基础学习笔记
多线程基础 -线程相关概念 程序(Program) 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存…...
从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践
作者:吴岐诗,杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言:融合数据湖与数仓的创新之路 在数字金融时代,数据已成为金融机构的核心竞争力。杭银消费金…...
给网站添加live2d看板娘
给网站添加live2d看板娘 参考文献: stevenjoezhang/live2d-widget: 把萌萌哒的看板娘抱回家 (ノ≧∇≦)ノ | Live2D widget for web platformEikanya/Live2d-model: Live2d model collectionzenghongtu/live2d-model-assets 前言 网站环境如下,文章也主…...
抽象类和接口(全)
一、抽象类 1.概念:如果⼀个类中没有包含⾜够的信息来描绘⼀个具体的对象,这样的类就是抽象类。 像是没有实际⼯作的⽅法,我们可以把它设计成⼀个抽象⽅法,包含抽象⽅法的类我们称为抽象类。 2.语法 在Java中,⼀个类如果被 abs…...

