SpringBoot 是一个基于 Spring 的快速开发框架,也是 SpringCloud 构建微服务分布式系统的基础设施。本文主要介绍如何通过 SpringBoot 快速搭建 DolphinDB 微服务,并且基于 Mybatis 操作 DolphinDB 数据库。
本项目实现了物联网中的一个典型场景:
环境配置:
首先我们通过 IDEA 创建 Springboot 项目,根据下图依次检查选项内容。
下载 IDEA Ultimate 版本
如下图所示,在左侧搜索框中搜索并添加项目需要的依赖(红色方框中的内容)。之后点击 Create 创建项目。
等待项目加载完成,确认 pom 文件中的依赖和版本号是否正确:
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.7.3
com.dolphindb
demo
0.0.1-SNAPSHOT
demo
demo
1.8
org.springframework.boot
spring-boot-starter-jdbc
org.springframework.boot
spring-boot-starter-web
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.2.2
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin
org.projectlombok
lombok
在 pom 文件中,参照如下代码添加 fastjson 和 mqtt 依赖。添加依赖后可点击右上角刷新按钮,下载相应的依赖包。
com.alibaba
fastjson
1.2.83
org.eclipse.paho
org.eclipse.paho.client.mqttv3
1.2.2
如下图所示,在项目中创建需要的目录:
项目整体框架搭建好后,下面进行数据库的准备工作。
打开 DolphinDB 官网 (DolphinDB 丨浙江智臾科技有限公司) 下载 DolphinDB server (Linux)。下载后将其上传到 Linux 服务器,解压并运行 dolphindb/server/dolphindb,开启服务。关于 DolphinDB 数据库部署的详细介绍请参考部署教程。
若结果如下图所示,则表示成功开启 DolphinDB 服务。
在 DolphinDB 官网下载并运行 DolphinDB GUI。关于 GUI 的下载及使用方法请参考 部署教程。
下载 建库建表脚本,并将脚本拷贝到 GUI 中运行。参考 编程语言介绍 了解更多 DolphinDB 编程语言。
在执行脚本之前,需配置流表数据持久化的目录路径(在 DolphinDB/server/dolphindb.cfg 文件中添加 persistenceDir=/home/Software/DolphinDB/data
,根据实际情况调整目录即可)。当内存中的流数据表的行数超过持久化流表(enableTableShareAndPersistence)设置值时,系统会将内存中的部分数据持久化到配置的目录中。
如下图所示,完成数据库的创建。
DolphinDB 还提供其他形式的客户端,详情参考 DolphinDB 客户端软件教程。
建立两个表:
这两个表的结构一致,一共 12 个字段,time 表示时间戳,id 为设备唯一编码,其他字段可以是一些采集的物理量(如温度、湿度等数据)。
创建两个表的方案,既能满足快速写入和实时查询的需要,也能满足大批数据查询的需要,适用于大部分应用场景。
pom 文件中添加 JDBC 依赖并且刷新 Maven。
com.dolphindb
jdbc
1.30.19.1
在 application.properties 中进行数据库相关属性的配置。本文只配了几个必要的配置,其他参数可自行配置,参数说明详见 官方配置文档。请注意,以下参数中 spring.datasource.url
请调整为用户实际使用的地址。
server.port=8888
spring.datasource.url=jdbc:dolphindb://192.168.1.182:8848?databasePath=dfs://DemoDB
spring.datasource.username=admin
spring.datasource.password=123456
spring.datasource.driver-class-name=com.dolphindb.jdbc.Driver
mybatis.mapper-locations=classpath:/mapper/*.xml
mybatis.configuration.auto-mapping-behavior=full
运行项目,启动后控制台输出如下:
完成前面的步骤后,就可以编写具体代码。
本节主要是代码的编写工作。需要在相应的目录下编写代码。
DemoEntity 对应数据库中 Demo 表的实体类,即为本案例中创建的流表和分区表所对应的类。
@Data
public class DemoEntity {
@JSONField(name="time", format = "yyyy-MM-dd HH:mm:ss")
private Date time;
@JSONField(name="id")
private long id;
@JSONField(name="f0")
private float f0;
@JSONField(name="f1")
private float f1;
@JSONField(name="f2")
private float f2;
@JSONField(name="f3")
private float f3;
@JSONField(name="f4")
private float f4;
@JSONField(name="f5")
private float f5;
@JSONField(name="f6")
private float f6;
@JSONField(name="f7")
private float f7;
@JSONField(name="f8")
private float f8;
@JSONField(name="f9")
private float f9;
}
定义分区表的数据查询接口:DemoDfsDao
@Mapper
public interface DemoDfsDao {
List queryByTimespan(@Param("start") Date start, @Param("end") Date end);
}
定义流表的数据写入和查询接口:DemoStreamDao
@Mapper
public interface DemoStreamDao {
void insert(DemoEntity demoEntity);
List queryByTimespan(@Param("start") Date start, @Param("end") Date end);
}
定义分区表的业务层查询接口:DemoDfsService
public interface DemoDfsService {
List queryByTimespan(Date start, Date end);
}
定义流表的业务层写入和查询接口:DemoStreamService
public interface DemoStreamService {
void insert(DemoEntity demoEntity);
List queryByTimespan(Date start, Date end);
}
定义分区表的业务层实现:DemoDfsServiceImpl
@Service
public class DemoDfsServiceImpl implements DemoDfsService {
@Autowired
DemoDfsDao demoDfsDao;
@Override
public List queryByTimespan(Date start, Date end) {
List demoEntities = demoDfsDao.queryByTimespan(start, end);
return demoEntities;
}
}
定义流表的业务层实现类:DemoStreamServiceImpl
@Service
public class DemoDfsServiceImpl implements DemoDfsService {
@Autowired
DemoDfsDao demoDfsDao;
@Override
public List queryByTimespan(Date start, Date end) {
List demoEntities = demoDfsDao.queryByTimespan(start, end);
return demoEntities;
}
}
定义对外数据传输类:QueryArg
@Data
public class QueryArg {
private Date startTime,endTime;
}
展现层类的定义
@RestController
public class DemoController {
@Autowired
DemoDfsServiceImpl demoDfsService;
@Autowired
DemoStreamServiceImpl demoStreamService;
@PostMapping("/queryStream")
public List queryStream(@RequestBody() QueryArg arg) {
return demoStreamService.queryByTimespan(arg.getStartTime(), arg.getEndTime());
}
@PostMapping("/queryDfs")
public List queryDfs(@RequestBody() QueryArg arg) {
return demoDfsService.queryByTimespan(arg.getStartTime(), arg.getEndTime());
}
}
Mqtt 客户端的定义,用于连接 EMQX,接收 MQTTX 发过来的消息并插入到数据库中。
@Component
public class DemoMqttClient {
Logger logger = LoggerFactory.getLogger(DemoMqttClient.class);
@Autowired
private DemoStreamService mDemoStreamService;
private MqttCallback onMessageCallback=new MqttCallback() {
public void connectionLost(Throwable cause) {
// 连接丢失后,进行重连
System.out.println("连接断开,可以做重连");
try {
reConnect();
}catch (Exception e){
System.out.println("重连失败,错误:"+e.toString());
}
}
public void messageArrived(String topic, MqttMessage message) throws Exception {
try{
// 将接收到的信息转换为实体类对象并插入流表
List demoEntities = JSON.parseArray(new String(message.getPayload()), DemoEntity.class);
for(DemoEntity entity : demoEntities){
mDemoStreamService.insert(entity);
}
}catch (Exception e){
e.printStackTrace();
}
}
public void deliveryComplete(IMqttDeliveryToken token) {
}
};
public final String HOST = "tcp://127.0.0.1:1883"; //ip 地址
public final String TOPIC = "test"; // 订阅主题
public final String clientId = "test"; // 客户端 id,不能重复
private MqttClient client;
private MqttConnectOptions connOpts;
private String userName = "admin";// 登录名
private String passWord = "123456";// 密码
@PostConstruct
public void init() {
start();
}
public void start() {
try {
client = new MqttClient(HOST, clientId, new MemoryPersistence());
connOpts = new MqttConnectOptions();
connOpts.setCleanSession(true); // 清空 session
connOpts.setUserName(userName);
connOpts.setPassword(passWord.toCharArray());
connOpts.setConnectionTimeout(10);// 设置超时时间
connOpts.setKeepAliveInterval(20);// 设置会话心跳时间
client.setCallback(onMessageCallback); // 设置回调函数
System.out.println("Connecting to broker:" + HOST);
client.connect(connOpts); // 创立连接
client.subscribe(TOPIC); // 订阅
} catch (Exception e) {
e.printStackTrace();
}
}
public void close() {
try {
this.client.close();
} catch (MqttException e) {
e.printStackTrace();
}
}
public void reConnect() throws Exception {
if (this.client != null) {
this.logger.info("开始重连");
this.client.connect(this.connOpts);
this.client.subscribe(TOPIC);
}
}
}
DemoDfs.xml 描述分区表的查询 SQL 实现。
DemoStream.xml 描述流表的查询和写入 SQL 实现。
insert into DemoStream values(#{time},#{id},#{f0},#{f1},#{f2},#{f3},#{f4},#{f5},#{f6},#{f7},#{f8},#{f9})
全部代码文件编写完成后,显示如下目录结构:
官网下载 EMQX EMQX: 大规模分布式物联网 MQTT 消息服务器。
下载 EMQX 完成后解压,在文件夹的 bin 目录处输入 cmd,打开命令窗口:
在命令窗口中执行 emqx start,开启 EMQX:
打开浏览器,输入 localhost:18083 登 EMQX Dashboard 界面,输入用户名密码登录(默认用户名 admin,密码 public)。
打开 下载的 MQTTX 客户端,新建连接,配置连接信息,点击 connect 按钮进行连接。连接后在 EMQX Dashboard 的连接管理页面查看连接成功的客户端。
至此,基于 DolphinDB 的 Springboot 项目已经搭建完成,接下来我们将使用一个简单的例子进行测试。
通过 MQTTX 客户端模拟物联网设备,发送实时数据至服务器。
通过 Postman 模拟前端网页调用后端接口,实现以下查询:
本教程只是简单模拟了物联网设备的数据集结构,模拟的数据规模也十分小,仅供参考。
准备好测试工具,下载 Postman。
首先启动 Springboot 项目。
使用 MQTTX 客户端向 EMQX 发送信息,发送消息的 topic 为 test。
发送的消息如下,time 为数据采集的时间,id 为设备编码,f0-f9 为设备的各个参数。
[
{
"time": "2022-09-15 20:01:54",
"id": 7,
"f0": 51.31,
"f1": 5.62,
"f2": 32.64,
"f3": 1.33,
"f4": 15.11,
"f5": 366.45,
"f6": 5.76,
"f7": 2.53,
"f8": 2.14,
"f9": 203.5
},
{
"time": "2022-09-15 20:01:45",
"id": 8,
"f0": 51.31,
"f1": 54.62,
"f2": 3.64,
"f3": 11.83,
"f4": 15.81,
"f5": 66.45,
"f6": 55.56,
"f7": 52.53,
"f8": 6.12,
"f9": 43.51
}
]
发送成功后可在 GUI 中运行 select * from DemoStream order by time desc,查看插入的数据:
打开 postman,以 post 方式发送请求 http://localhost:8888/queryStream
,查询一小时内的实时数据,下面是请求的格式:
{
"startTime":"2022-09-15T12:00:00.955+00:00",
"endTime":"2022-09-15T13:00:00.955+00:00"
}
以 post 方式发送请求 http://localhost:8888/queryDfs
,查询一年内的历史数据,下面是请求的格式:
{
"startTime":"2022-09-15T21:00:00.955+00:00",
"endTime":"2021-09-15T21:00:00.955+00:00"
}