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...

接口测试中缓存处理策略
在接口测试中,缓存处理策略是一个关键环节,直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性,避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明: 一、缓存处理的核…...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...

2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...

Cinnamon修改面板小工具图标
Cinnamon开始菜单-CSDN博客 设置模块都是做好的,比GNOME简单得多! 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

前端开发面试题总结-JavaScript篇(一)
文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包(Closure)?闭包有什么应用场景和潜在问题?2.解释 JavaScript 的作用域链(Scope Chain) 二、原型与继承3.原型链是什么?如何实现继承&a…...

html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...

初探Service服务发现机制
1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能:服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源…...
OD 算法题 B卷【正整数到Excel编号之间的转换】
文章目录 正整数到Excel编号之间的转换 正整数到Excel编号之间的转换 excel的列编号是这样的:a b c … z aa ab ac… az ba bb bc…yz za zb zc …zz aaa aab aac…; 分别代表以下的编号1 2 3 … 26 27 28 29… 52 53 54 55… 676 677 678 679 … 702 703 704 705;…...