基于 DolphinDB 搭建微服务的 SpringBoot 项目

SpringBoot 是一个基于 Spring 的快速开发框架,也是 SpringCloud 构建微服务分布式系统的基础设施。本文主要介绍如何通过 SpringBoot 快速搭建 DolphinDB 微服务,并且基于 Mybatis 操作 DolphinDB 数据库。本项目中 Mybatis 框架对 DolphinDB 的支持:所有 DolphinDB 基础数据类型的增删改查;部分类型需要 Mybatis 框架提供的 handler 进行转换。

1. 创建 SpringBoot 项目

1.1 环境配置

  • JDK21
  • IntelliJ IDEA 2025.1.1.1 (Community Edition)
  • Spring Boot 3.5.4
  • DolphinDB 3.00.5(Linux)
  • DolphinDB JDBC 3.00.5
  • Postman

1.2 新建项目

进入 Spring Initializr,Project 选择 Maven,Language 选择 Java,Spring Boot 选择 3.5.11 及以上,Project Metadata 的 Group 改成 com.dolphindb,Dependencies 添加 Lombok,Spring Web 和 MyBatis Framework。单击下方的 GENERATE 生成 SpringBoot 项目。

1. 图 1-1 Spring Initializr

1.3 打开项目

将 1.2 下载的安装包解压后,使用 IEDA 打开项目。

2. 图 1-2 IEDA 项目

1.4 新建目录

新建目录,构建标准的 Spring MVC 目录结构:

3. 图 1-3 新建目录

项目整体框架搭建好后,下面进行数据库的准备工作。

2. 创建 DolphinDB 数据库

2.1 创建数据库,建立 包含基础数据类型 的数据表

在 DolphinDB 中执行以下脚本,创建数据库表。

colName = `BoolValue`CharValue`ShortValue`IntValue`LongValue`Date`Time`Second`Datetime`Timestamp`Nanotime`Nanotimestamp`Minute`FloatValue`DoubleValue`Symbol`String`DateHour`Blob
colType = [BOOL,CHAR,SHORT,INT,LONG,DATE,TIME,SECOND,DATETIME,TIMESTAMP,NANOTIME,NANOTIMESTAMP,MINUTE,FLOAT,DOUBLE,SYMBOL,STRING,DATEHOUR,BLOB]
t = table(100:0,colName,colType)
engine = "TSDB"
dbName = "dfs://example"
tbName = "example"
years = sort(distinct(yearBegin(1985.01.01..2055.01.01)))
directory = dbName
db = database(directory, RANGE, years, engine=engine)
createPartitionedTable(db,t,tbName,`Timestamp,,`Date)

构建完成的库表如图所示:

4. 图 2-1 库表

包含了 DolphinDB 的基础数据类型,之后将对该表进行增删改查操作。

2.2 添加 DolphinDB JDBC 依赖

在 pom 文件中添加 DolphinDB 的 JDBC 依赖并且刷新 Maven。

<dependency>
    <groupId>com.dolphindb</groupId>
    <artifactId>jdbc</artifactId>
    <version>3.00.5.0</version>
</dependency>

2.3 项目配置

在 application.properties 中配置连接 DolphinDB 数据库所需的参数。本文只配了几个必要的配置,其他参数可自行配置,参数说明详见官方配置文档。请注意,以下参数中 spring.datasource.url 请调整为用户实际使用的地址。

server.port=18888
spring.datasource.url=jdbc:dolphindb://127.0.0.1:8848?user=admin&password=123456&databasePath=dfs://example
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
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

运行项目,启动后控制台输出如下:

5. 图 2-2 控制台输出

完成前面的准备步骤后,就可以编写具体的代码了。

3. 基础类型的增删改查

本章节将编写代码实现基础数据类型的增删改查接口,需要注意 DolphinDB JDBC 的数据类型与 Java 原生类型的对应关系。

3.1 entity 目录

ExampleEntity 是实体类,对应 dfs://example 数据库中的 example 表。其中 DolphinDB JDBC 的数据类型与 Java 原生类型的对应关系需要参照 DolphinDB 官网 Java 手册的数据类型章节。

创建 src/main/java/com/dolphindb/demo/entity/ExampleEntity.java 实体类。

package com.dolphindb.demo.entity;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Date;
import lombok.Data;
@Data
public class ExampleEntity {
    private Boolean boolValue;
    private Byte charValue;
    private Short shortValue;
    private Integer intValue;
    private Long longValue;
    private LocalDate date;
    private Date time;
    private LocalTime second;
    private LocalDateTime datetime;
    private LocalDateTime timestamp;
    private LocalDateTime nanotime;
    private LocalDateTime nanotimestamp;
    private LocalTime minute;
    private Float floatValue;
    private Double doubleValue;
    private String symbol;
    private String string;
    private LocalDateTime dateHour;
    private String blob;
}

3.2 增删改查

定义实体类后,即可开始编写代码实现针对 DolphinDB 数据库的增删改查接口。

3.2.1 插入

  • dao 目录:在 dao 目录下创建一个接口ExampleDao。并定义 insertExampleData(ExampleEntity ExampleEntity) 作为插入数据的方法。

    创建 src/main/java/com/dolphindb/demo/dao/ExampleDao.java 接口。

    package com.dolphindb.demo.dao;
    import com.dolphindb.demo.entity.ExampleEntity;
    import org.apache.ibatis.annotations.Mapper;
    @Mapper
    public interface ExampleDao {
        void insertExampleData(ExampleEntity ExampleEntity);
    }
  • service 目录:创建服务的接口 ExampleService

    创建 src/main/java/com/dolphindb/demo/service/ExampleService.java 接口。

    package com.dolphindb.demo.service;
    import com.dolphindb.demo.entity.ExampleEntity;
    public interface ExampleService {
        void insertExampleData(ExampleEntity exampleEntity);
    }

    在 service 目录下面创建 impl 文件夹,创建服务的业务层实现:ExampleServiceImpl

    创建 src/main/java/com/dolphindb/demo/service/impl/ExampleServiceImpl.java 类。

    package com.dolphindb.demo.service.impl;
    import com.dolphindb.demo.dao.ExampleDao;
    import com.dolphindb.demo.entity.ExampleEntity;
    import com.dolphindb.demo.service.ExampleService;
    import jakarta.annotation.Resource;
    import org.springframework.stereotype.Service;
    @Service
    public class ExampleServiceImpl implements ExampleService {
        @Resource
        ExampleDao exampleDao;
        @Override
        public void insertExampleData(ExampleEntity exampleEntity) {
            exampleDao.insertExampleData(exampleEntity);
        }
    }
  • controller 目录:ExampleController 文件

    创建 src/main/java/com/dolphindb/demo/controller/ExampleController.java 类。

    package com.dolphindb.demo.controller;
    import com.dolphindb.demo.entity.ExampleEntity;
    import com.dolphindb.demo.service.ExampleService;
    import jakarta.annotation.Resource;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RestController;
    import java.io.IOException;
    @RestController
    public class ExampleController {
        @Resource
        ExampleService exampleService;
        @PostMapping("/insertExampleData")
        public ResponseEntity<String> insertExampleData(@RequestBody ExampleEntity exampleEntity) throws IOException {
            exampleService.insertExampleData(exampleEntity);
            return ResponseEntity.ok("insert");
        }
    }
  • mapper 目录:ExampleMapper.xml 文件

    创建 src/main/resources/mapper/ExampleMapper.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="com.dolphindb.demo.dao.ExampleDao" >
        <insert id="insertExampleData">
            insert into loadTable("dfs://example", "example")
            values(
            #{boolValue},
            #{charValue},
            #{shortValue},
            #{intValue},
            #{longValue},
            #{date},
            #{time},
            #{second},
            #{datetime},
            #{timestamp},
            #{nanotime},
            #{nanotimestamp},
            #{minute},
            #{floatValue},
            #{doubleValue},
            #{symbol},
            #{string},
            #{dateHour},
            #{blob}
            )
        </insert>
    </mapper>

全部代码编写完成后,目录结构如下:

6. 图 3-1 目录结构

重启项目后,可以使用 Postman 来测试接口,请求方式选择 POST,URL 填写 127.0.0.1:18888/insertExampleData,Header 中必要的参数是 key:Content-Type,Value:application/json。Body 选择 raw,并且选择 JSON。

JSON 字符串的内容:

{
  "boolValue": true,
  "charValue": 99,
  "shortValue": 56,
  "intValue": 2048,
  "longValue": -1603669167,
  "date": "2015-10-05",
  "time": "1970-01-01T11:38:10.000+00:00",
  "second": "01:06:31",
  "datetime": "2014-11-11T11:07:53",
  "timestamp": "2000-06-18T07:05:29",
  "nanotime": "1970-01-01T07:50:31.075101949",
  "nanotimestamp": "2014-06-13T18:06:58.061041998",
  "minute": "1970-01-01T10:54:10.000",
  "floatValue": 7.3313475,
  "doubleValue": 0.685568745069626,
  "symbol": "Zb",
  "string": "j4RAJ",
  "dateHour": "0552-06-13T20:00:00",
  "blob": "iD"
}

发送 POST 请求后,如果插入成功,会在 Body 中收到 insert 的返回。

7. 图 3-2 insert 返回结果

此时,到数据库查询 loadTable("dfs://example", "example") 表。

select * from loadTable("dfs://example", "example")

结果如下:

8. 图 3-3 查询结果

可以看到数据已经成功插入了。

3.2.2 查询

继续编写数据库的查询代码,可以使用多种 where 条件来查询数据库。

  • ExampleDao 文件

    ExampleDao 文件中添加数据查询方法。

    import java.time.LocalDate;
    import java.util.List;
    List<ExampleEntity> getExampleData(); // 查询所有数据
    List<ExampleEntity> getExampleDataByDate(LocalDate date); // 根据日期查询
    List<ExampleEntity> getExampleDataBetweenIntValue(int startIntValue, int endIntValue); // 使用 between and 查询
    List<ExampleEntity> getExampleDataByIds(int[] intValues); // 使用 in 查询
  • ExampleMapper.xml 文件

    ExampleMapper.xml 文件实现这些方法。

    <resultMap id="ExampleEntityMap" type="com.dolphindb.demo.entity.ExampleEntity">
    </resultMap>
    <select id="getExampleData" resultMap="ExampleEntityMap">
        select * from loadTable("dfs://example", "example")
    </select>
    <select id="getExampleDataByDate" resultMap="ExampleEntityMap">
        select * from loadTable("dfs://example", "example") where Date = #{date}
    </select>
    <select id="getExampleDataBetweenIntValue" resultMap="ExampleEntityMap">
        select * from loadTable("dfs://example", "example") where IntValue between #{startIntValue} and #{endIntValue}
    </select>
    <select id="getExampleDataByIds" resultMap="ExampleEntityMap">
        SELECT * FROM example
        WHERE IntValue IN
        <foreach collection="intValues" item="intValue" open="[" separator="," close="]">
            #{intValue}
        </foreach>
    </select>

    可以看到 getExampleDataByIds 这个方法,我们直接使用的表名来查询,这是因为我们在 spring.datasource.url 配置项中配置了 databasePath=dfs://example,该配置项可以自动加载库表,因此这里不需要使用 loadTable 的方式来加载表。

  • ExampleService 文件

    import java.io.IOException;
    import java.time.LocalDate;
    import java.util.List;
    List<ExampleEntity> getExampleData() throws IOException;
    List<ExampleEntity> getExampleDataByDate(LocalDate date);
    List<ExampleEntity> getExampleDataBetweenIntValue(int startIntValue, int endIntValue);
    List<ExampleEntity> getExampleDataByIds(int[] intValues);

    ExampleServiceImpl 文件中实现这些方法。

    import java.io.IOException;
    import java.time.LocalDate;
    import java.util.List;
    @Override
    public List<ExampleEntity> getExampleData() throws IOException {
        return exampleDao.getExampleData();
    }
    @Override
    public List<ExampleEntity> getExampleDataByDate(LocalDate date) {
        return exampleDao.getExampleDataByDate(date);
    }
    @Override
    public List<ExampleEntity> getExampleDataBetweenIntValue(int startIntValue, int endIntValue) {
        return exampleDao.getExampleDataBetweenIntValue(startIntValue, endIntValue);
    }
    @Override
    public List<ExampleEntity> getExampleDataByIds(int[] intValues) {
        return exampleDao.getExampleDataByIds(intValues);
    }
  • ExampleController 文件

    import java.time.LocalDate;
    import java.util.List;
    import org.springframework.format.annotation.DateTimeFormat;
    import org.springframework.web.bind.annotation.*;
    //http://127.0.0.1:18888/getExampleData
    @GetMapping("/getExampleData")
    public List<ExampleEntity> getExampleData() throws IOException {
        return exampleService.getExampleData();
    }
    //http://127.0.0.1:18888/getExampleDataByDate?Date=2015-10-03
    @GetMapping("/getExampleDataByDate")
    public List<ExampleEntity> getExampleDataByDate(@RequestParam("Date") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date) throws IOException {
        return exampleService.getExampleDataByDate(date);
    }
    //http://127.0.0.1:18888/getExampleDataBetweenIntValue?startIntValue=0&endIntValue=2049
    @GetMapping("/getExampleDataBetweenIntValue")
    public List<ExampleEntity> getExampleDataBetweenIntValue(
            @RequestParam("startIntValue") int startIntValue,
            @RequestParam("endIntValue") int endIntValue) throws IOException {
        return exampleService.getExampleDataBetweenIntValue(startIntValue, endIntValue);
    }
    //http://127.0.0.1:18888/getExampleDataByIds?IntValues=2048,2049
    @GetMapping("/getExampleDataByIds")
    public List<ExampleEntity> getExampleDataByIds(@RequestParam("IntValues") int[] IntValues){
        return exampleService.getExampleDataByIds(IntValues);
    }

这样就添加了四个接口:

  1. getExampleData:查询所有数据。例如:GET 请求http://127.0.0.1:18888/getExampleData
  2. getExampleDataByDate:根据 Date 字段查询数据。例如:GET 请求http://127.0.0.1:18888/getExampleDataByDate?Date=2015-10-05
  3. getExampleDataBetweenIntValue:根据 IntValue 字段查询数据,查询条件是 between and。例如:GET 请求 http://127.0.0.1:18888/getExampleDataBetweenIntValue?startIntValue=0&endIntValue=2049
  4. getExampleDataByIds:根据 IntValue 字段查询数据,查询条件是 in。例如:GET 请求http://127.0.0.1:18888/getExampleDataByIds?IntValues=2048,2049

3.2.3 更新和删除

同样的,我们也可以进行更新与删除的操作。

  • ExampleDao 文件

    ExampleDao 文件中添加数据更新和删除的方法。

    import java.util.HashMap;
    long deleteExampleDataByDate(LocalDate date);
    long updateExampleDataByDate(LocalDate startDate, LocalDate endDate, HashMap<String, Object> updateFields);

    分别是根据日期删除数据以及根据实际范围进行数据更新。

  • ExampleMapper.xml 文件

    ExampleMapper.xml 文件中实现这些方法。

    <update id="updateExampleDataByDate">
        UPDATE loadTable("dfs://example", "example")
        <set>
            <foreach collection="updateFields" index="key" item="value" separator=",">
                ${key} = #{value}
            </foreach>
        </set>
        WHERE Date BETWEEN #{startDate} AND #{endDate}
    </update>
    <delete id="deleteExampleDataByDate">
        delete from loadTable("dfs://example", "example") where Date = #{date}
    </delete>
  • ExampleService 文件

    import java.util.HashMap;
    long deleteExampleDataByDate(LocalDate date);
    long updateExampleDataByDate(LocalDate startDate, LocalDate endDate, HashMap<String, Object> updateFields);

    并在 ExampleServiceImpl 文件中实现这些方法。

    import java.util.HashMap;
    @Override
    public long deleteExampleDataByDate(LocalDate date) {
        return exampleDao.deleteExampleDataByDate(date);
    }
    @Override
    public long updateExampleDataByDate(LocalDate startDate, LocalDate endDate, HashMap<String, Object> updateFields) {
        return exampleDao.updateExampleDataByDate(startDate, endDate, updateFields);
    }
  • ExampleController 文件

    import com.dolphindb.demo.converter.GenericEntityConverter;
    import java.util.HashMap;
    //http://127.0.0.1:18888/deleteExampleDataByDate?Date=2015-10-03
    @DeleteMapping("/deleteExampleDataByDate")
    public ResponseEntity<String> deleteExampleDataByDate(@RequestParam("Date") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date) throws IOException {
        long deleteNum = exampleService.deleteExampleDataByDate(date);
        return ResponseEntity.ok("删除行数: " + deleteNum);
    }
    @PutMapping("/updateExampleDataByDate")
    public ResponseEntity<String> updateExampleDataByDate(@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
                                                          @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate,
                                                          @RequestBody HashMap<String, Object> updateFields
    ){
        HashMap<String, Object> updateFieldsConvertByExampleEntity = GenericEntityConverter.convertToTypedMap(updateFields, ExampleEntity.class);
        long updateNum = exampleService.updateExampleDataByDate(startDate, endDate, updateFieldsConvertByExampleEntity);
        return ResponseEntity.ok("更新行数: " + updateNum);
    }

可以看到,在 updateExampleDataByDate 方法中,用到了 GenericEntityConverter.convertToTypedMap 方法。这个方法的作用是根据提供的实体类,来更改 HashMap 中的数据的类型,使得数据可以和 DolphinDB 的数据类型对应。

需要在 converter 文件夹中创建 GenericEntityConverter 类。其具体内容如下:

package com.dolphindb.demo.converter;
import java.lang.reflect.Field;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
public class GenericEntityConverter {
    private static final DateTimeFormatter DATE_FORMATTER =
            DateTimeFormatter.ISO_LOCAL_DATE;
    private static final DateTimeFormatter DATETIME_FORMATTER =
            DateTimeFormatter.ISO_LOCAL_DATE_TIME;
    private static final DateTimeFormatter TIME_FORMATTER =
            DateTimeFormatter.ofPattern("HH:mm:ss"); // 匹配 "09:16:32" 格式
    private static final DateTimeFormatter[] DATE_TIME_FORMATTERS = {
            DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"),  // 带毫秒和时区:1970-01-01T11:38:10.000+00:00
            DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS"),    // 带毫秒无时区:1970-01-01T11:38:10.000
            DateTimeFormatter.ISO_OFFSET_DATE_TIME,                      // 标准带时区格式(无毫秒)
            DateTimeFormatter.ISO_LOCAL_DATE_TIME,                        // 标准无时区格式
            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")            // 空格分隔格式
    };
    /**
     * 将实体对象转换为HashMap,并确保字段类型与目标实体类定义一致
     * @param entity 实体对象
     * @param targetEntityClass 目标实体类的Class对象(用于获取字段类型)
     * @return 转换后的HashMap
     */
    public static <T> HashMap<String, Object> convertToTypedMap(Object entity, Class<T> targetEntityClass) {
        HashMap<String, Object> resultMap = new HashMap<>();
        try {
            Field[] targetFields = targetEntityClass.getDeclaredFields();
            Map<String, Field> fieldMap = Arrays.stream(targetFields)
                    .collect(Collectors.toMap(
                            field -> field.getName().toLowerCase(), // 统一转为小写匹配
                            field -> field
                    ));
            for (Map.Entry<String, Object> entry: ((HashMap<String, Object>) entity).entrySet()) {
                String fieldName = entry.getKey().toLowerCase(); // 统一小写
                if (fieldMap.containsKey(fieldName)) {
                    Field targetField = fieldMap.get(fieldName);
                    targetField.setAccessible(true);
                    Object convertedValue = convertValue(entry.getValue(), targetField.getType());
                    resultMap.put(targetField.getName(), convertedValue); // 使用原始字段名存入Map
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("实体转Map失败: " + e.getMessage(), e);
        }
        return resultMap;
    }
    /**
     * 类型转换核心逻辑
     */
    private static Object convertValue(Object rawValue, Class<?> targetType) {
        if (rawValue == null) return null;
        try {
            String strValue = rawValue.toString();
            if (targetType == boolean.class || targetType == Boolean.class) {
                return Boolean.parseBoolean(strValue);
            } else if (targetType == short.class || targetType == Short.class) {
                return Short.parseShort(strValue);
            } else if (targetType == int.class || targetType == Integer.class) {
                return Integer.parseInt(strValue);
            } else if (targetType == long.class || targetType == Long.class) {
                return Long.parseLong(strValue);
            } else if (targetType == float.class || targetType == Float.class) {
                return Float.parseFloat(strValue);
            } else if (targetType == double.class || targetType == Double.class) {
                return Double.parseDouble(strValue);
            } else if (targetType == LocalDate.class) {
                return LocalDate.parse(strValue, DATE_FORMATTER);
            } else if (targetType == LocalDateTime.class) {
                return LocalDateTime.parse(strValue, DATETIME_FORMATTER);
            } else if (targetType == LocalTime.class) {
                return LocalTime.parse(strValue, TIME_FORMATTER); // 使用自定义格式
            } else if (targetType == YearMonth.class) {
                return YearMonth.parse(strValue);
            } else if (targetType == Date.class) {
                for (DateTimeFormatter formatter: DATE_TIME_FORMATTERS) {
                    try {
                        if (formatter == DateTimeFormatter.ISO_OFFSET_DATE_TIME) {
                            // 处理带时区的格式(包括毫秒)
                            return Date.from(Instant.from(formatter.parse(strValue)));
                        } else {
                            // 处理无时区的格式
                            LocalDateTime localDateTime = LocalDateTime.parse(strValue, formatter);
                            return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
                        }
                    } catch (Exception ignored) {
                        // 继续尝试下一个格式
                    }
                }
                throw new IllegalArgumentException("无法解析时间格式: " + strValue);
            } else if (targetType == int[].class) {
                return ((ArrayList<?>) rawValue).stream()
                        .mapToInt(o -> Integer.parseInt(o.toString()))
                        .toArray();
            }
            return rawValue; // 其他类型保持原样
        } catch (Exception e) {
            throw new IllegalArgumentException(
                    "字段类型转换失败: " + rawValue + " -> " + targetType.getSimpleName(), e);
        }
    }
}

我们新创建了数据更新和删除的接口。

  1. 数据更新,使用 Postman 测试:

    请求方式选择 PUT,URL 填写 127.0.0.1:18888/updateExampleDataByDate?startDate=2015-10-04&endDate=2015-10-05,Header 中必要的参数是 key:Content-Type,Value:application/json。Body 选择 raw,并且选择 JSON。

    9. 图 3-4 更新数据

    JSON 字符串的内容:

    {
        "boolValue": true,
        "charValue": 98,
        "shortValue": 12,
        "intValue": 205,
        "longValue": 160566267,
        "time": "1970-01-01T12:36:10.100+00:00",
        "second": "02:07:31",
        "datetime": "2015-11-11T11:07:53",
        "nanotime": "1970-01-01T07:50:31.075101949",
        "nanotimestamp": "2014-06-13T18:06:58.061041998",
        "floatValue": 9.3313475,
        "minute": "05:17:10",
        "doubleValue": 0.685568745069626,
        "symbol": "Zb",
        "string": "j4RAJ",
        "dateHour": "0552-06-13T20:00:00",
        "blob": "iD"
    }

    发送 PUT 请求后,如果更新成功,会在 Body 中收到更新行数的返回。

    10. 图 3-5 更新行数

    此时,到数据库查询 loadTable("dfs://example", "example") 表。

    select * from loadTable("dfs://example", "example")
    11. 图 3-6 查询结果

    可以发现数据已经更新成功了。

  2. 删除接口 deleteExampleDataByDate,可以删除指定日期的数据

    请求方式选择 DELETE,URL 填写http://127.0.0.1:18888/deleteExampleDataByDate?Date=2015-10-05,Header 中必要的参数是 key:Content-Type,Value:application/json。

    12. 图 3-7 删除数据

    结果如下:

    13. 图 3-8 删除结果

3.3 实现 DolphinDB 特殊的插入方法:upsert!

upsert! 的作用是将新数据写入索引内存表、键值内存表,或者 DFS 表。若新数据的主键值已存在,更新该主键值的数据;否则添加数据。

  • ExampleDao 文件:

    添加批量 upsert 插入数据的方法。

    void upsertExampleData(List<ExampleEntity> exampleEntityList);
  • ExampleMapper.xml 文件:

    <insert id="upsertExampleData">
        exampleTmpTable = table(1:0, loadTable("dfs://example", "example").schema().colDefs.name, loadTable("dfs://example", "example").schema().colDefs.typeString);
        <foreach collection="list" item="item">
            insert into exampleTmpTable
            values(
            #{item.boolValue},
            #{item.charValue},
            #{item.shortValue},
            #{item.intValue},
            #{item.longValue},
            #{item.date},
            #{item.time},
            #{item.second},
            #{item.datetime},
            #{item.timestamp},
            #{item.nanotime},
            #{item.minute},
            #{item.nanotimestamp},
            #{item.floatValue},
            #{item.doubleValue},
            #{item.symbol},
            #{item.string},
            #{item.dateHour},
            #{item.blob});
        </foreach>
        upsert!(loadTable("dfs://example", "example"), exampleTmpTable,,["Date", "IntValue"]);
        select 1;
    </insert>

    该代码的逻辑是首先根据 loadTable("dfs://example", "example") 创建一个空表 exampleTmpTable,并且将数据全部插入到这个空表中,之后使用 upsert! 函数将数据插入到分布式表中。

  • ExampleService 文件:

    void upsertExampleData(List<ExampleEntity> exampleEntityList);
  • ExampleServiceImpl 文件中实现这个方法。

    @Override
    public void upsertExampleData(List<ExampleEntity> exampleEntityList) {
        exampleDao.upsertExampleData(exampleEntityList);
    }
  • ExampleController 文件:

    @PostMapping("/upsertExampleData")
    public ResponseEntity<String> upsertExampleData(@RequestBody List<ExampleEntity> exampleEntityList) throws IOException {
        exampleService.upsertExampleData(exampleEntityList);
        return ResponseEntity.ok("Upsert");
    }

可以使用 Postman 来测试接口,请求方式选择 POST,URL 填写 127.0.0.1:18888/upsertExampleData,Header 中必要的参数是 key:Content-Type,Value:application/json。Body 选择 raw,并且选择 JSON。可以连续发送两次 JSON,验证数据是否会更新。

第一个 JSON 字符串:

[
    {
        "boolValue": false,
        "charValue": 99,
        "shortValue": 56,
        "intValue": 2049,
        "longValue": -1603669167,
        "date": "2015-10-04",
        "time": "1970-01-01T11:38:10.000+00:00",
        "second": "01:06:31",
        "datetime": "2014-11-11T11:07:53",
        "timestamp": "2000-06-18T07:05:29",
        "nanotime": "1970-01-01T07:50:31.075101949",
        "nanotimestamp": "2014-06-13T18:06:58.061041998",
        "minute": "02:03:25",
        "floatValue": 7.3313475,
        "doubleValue": 0.685568745069626,
        "symbol": "Zb",
        "string": "j4RAJ",
        "dateHour": "0552-06-13T20:00:00",
        "blob": "iD"
    },
    {
        "boolValue": true,
        "charValue": 99,
        "shortValue": 56,
        "intValue": 2048,
        "longValue": 1603669167,
        "date": "2015-10-05",
        "time": "1970-01-01T11:38:10.000+00:00",
        "second": "01:06:31",
        "datetime": "2014-11-11T11:07:53",
        "timestamp": "2000-06-18T07:05:29",
        "nanotime": "1970-01-01T07:50:31.075101949",
        "nanotimestamp": "2014-06-13T18:06:58.061041998",
        "minute": "03:04:25",
        "floatValue": 7.3313475,
        "doubleValue": 0.685568745069626,
        "symbol": "Zb",
        "string": "j4RAJ",
        "dateHour": "0552-06-13T20:00:00",
        "blob": "iD"
    }
]

当 Postman 返回 Upsert 表示成功,此时查看数据库也成功插入了两条数据。

14. 图 3-9 查询结果

然后再发送第二条 POST 请求,第一行数据完全更改 dateintValue,而第二条数据保持 dateintValue 不变,仅更改其他值。

JSON 字符串:

[
    {
        "boolValue": true,
        "charValue": 99,
        "shortValue": 56,
        "intValue": 2050,
        "longValue": 6669167,
        "date": "2015-10-07",
        "time": "1970-01-01T11:38:10.000+00:00",
        "second": "01:06:31",
        "datetime": "2014-11-11T11:07:53",
        "timestamp": "2000-06-18T07:05:29",
        "nanotime": "1970-01-01T07:50:31.075101949",
        "nanotimestamp": "2014-06-13T18:06:58.061041998",
        "minute": "02:03:25",
        "floatValue": 7.3313475,
        "doubleValue": 0.685568745069626,
        "symbol": "Zb",
        "string": "j4RAJ",
        "dateHour": "0552-06-13T20:00:00",
        "blob": "new"
    },
    {
        "boolValue": true,
        "charValue": 99,
        "shortValue": 56,
        "intValue": 2048,
        "longValue": 16516221,
        "date": "2015-10-05",
        "time": "1970-01-01T11:38:10.000+00:00",
        "second": "01:06:31",
        "datetime": "2014-11-11T11:07:53",
        "timestamp": "2000-06-18T07:05:29",
        "nanotime": "1970-01-01T07:50:31.075101949",
        "nanotimestamp": "2014-06-13T18:06:58.061041998",
        "minute": "03:04:25",
        "floatValue": 8.3313475,
        "doubleValue": 0.685568745069626,
        "symbol": "Zb",
        "string": "j4RAJ",
        "dateHour": "0552-06-13T20:00:00",
        "blob": "old"
    }
]

发送 POST 请求后,再次查看数据库可以看到有三条数据,达成了不存在就插入,存在即更新的需求。

15. 图 3-10 查询结果

4. 特殊类型

除了基础类型外,DolphinDB 还有许多其他的特殊类型,接下来将编写针对 DolphinDB 特殊类型的增删改查接口。

4.1 DECIMAL

Decimal 类型在 DolphinDB JDBC 中对应的类型是 String,但在 DolphinDB 中无法使用 STING 类型对 DECIMAL 类型进行操作,所以我们要设置成在插入的时候使用 String,在其他操作时使用 double。

4.1.1 DolphinDB 建表

db = database("dfs://example")
t = table(1:0, ["Date", "Decimal32", "Decimal64", "Decimal128"], [DATE, DECIMAL32(6), DECIMAL64(6), DECIMAL128(6)])
createPartitionedTable(db, t, "decimalTable","Date",,"Date")

4.1.2 DECIMAL 增删改查

  • entity 文件夹

    创建 src/main/java/com/dolphindb/demo/entity/DecimalTableEntity.java 类。

    package com.dolphindb.demo.entity;
    import lombok.Data;
    import java.time.LocalDate;
    @Data
    public class DecimalTableEntity {
        private LocalDate date;
        private String decimal32;
        private String decimal64;
        private String decimal128;
    }

    可以看到,我们将 DECIMAL 类型设置为了 String 类型,在查询和插入的时候会作为 String 类型插入。

  • dao 文件夹

    创建 src/main/java/com/dolphindb/demo/dao/DecimalTableDao.java 类。

    package com.dolphindb.demo.dao;
    import com.dolphindb.demo.entity.DecimalTableEntity;
    import org.apache.ibatis.annotations.Mapper;
    import java.time.LocalDate;
    import java.util.HashMap;
    import java.util.List;
    @Mapper
    public interface DecimalTableDao {
        void insertDecimalTableData(DecimalTableEntity entity);
        List<DecimalTableEntity> getDecimalTableDataByDate(LocalDate date);
        long updateDecimalTableDataByDate(LocalDate date, HashMap<String, Object> updateFields);
        long deleteDecimalTableDataByDate(LocalDate date);
    }
  • mapper 文件夹

    创建 src/main/resources/mapper/DecimalTableMapper.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="com.dolphindb.demo.dao.DecimalTableDao" >
        <insert id="insertDecimalTableData">
            insert into decimalTable values(#{date}, #{decimal32}, #{decimal64}, #{decimal128})
        </insert>
        <update id="updateDecimalTableDataByDate">
            UPDATE decimalTable
            <set>
                <foreach collection="updateFields" index="key" item="value" separator=",">
                    ${key} = #{value}
                </foreach>
            </set>
            WHERE Date = #{date}
        </update>
        <select id="getDecimalTableDataByDate" resultType="com.dolphindb.demo.entity.DecimalTableEntity">
            select * from loadTable("dfs://example", "decimalTable") where Date = #{date}
        </select>
        <delete id="deleteDecimalTableDataByDate">
            delete from loadTable("dfs://example", "decimalTable") where Date = #{date}
        </delete>
    </mapper>
  • service 文件夹

    创建 src/main/java/com/dolphindb/demo/service/DecimalTableService.java 接口。

    package com.dolphindb.demo.service;
    import com.dolphindb.demo.entity.DecimalTableEntity;
    import java.time.LocalDate;
    import java.util.HashMap;
    import java.util.List;
    public interface DecimalTableService {
        void insertDecimalTableData(DecimalTableEntity entity);
        List<DecimalTableEntity> getDecimalTableDataByDate(LocalDate date);
        long updateDecimalTableDataByDate(LocalDate date, HashMap<String, Object> updateFields);
        long deleteDecimalTableDataByDate(LocalDate date);
    }

    并且创建 src/main/java/com/dolphindb/demo/service/impl/DecimalTableServiceImpl.java 类实现这些方法。

    package com.dolphindb.demo.service.impl;
    import com.dolphindb.demo.dao.DecimalTableDao;
    import com.dolphindb.demo.entity.DecimalTableEntity;
    import com.dolphindb.demo.service.DecimalTableService;
    import jakarta.annotation.Resource;
    import org.springframework.stereotype.Service;
    import java.time.LocalDate;
    import java.util.HashMap;
    import java.util.List;
    @Service
    public class DecimalTableServiceImpl implements DecimalTableService {
        @Resource
        DecimalTableDao decimalTableDao;
        @Override
        public void insertDecimalTableData(DecimalTableEntity decimalTableEntity) {
            decimalTableDao.insertDecimalTableData(decimalTableEntity);
        }
        @Override
        public List<DecimalTableEntity> getDecimalTableDataByDate(LocalDate date) {
            return decimalTableDao.getDecimalTableDataByDate(date);
        }
        @Override
        public long updateDecimalTableDataByDate(LocalDate date, HashMap<String, Object> updateFields) {
            return decimalTableDao.updateDecimalTableDataByDate(date, updateFields);
        }
        @Override
        public long deleteDecimalTableDataByDate(LocalDate date) {
            return decimalTableDao.deleteDecimalTableDataByDate(date);
        }
    }
  • controller 文件夹

    创建 src/main/java/com/dolphindb/demo/controller/DecimalTableController.java 类。

    package com.dolphindb.demo.controller;
    import com.dolphindb.demo.converter.GenericEntityConverter;
    import com.dolphindb.demo.entity.DecimalTableEntity;
    import com.dolphindb.demo.service.DecimalTableService;
    import jakarta.annotation.Resource;
    import org.springframework.format.annotation.DateTimeFormat;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.*;
    import java.io.IOException;
    import java.time.LocalDate;
    import java.util.HashMap;
    import java.util.List;
    @RestController
    public class DecimalTableController {
        @Resource
        DecimalTableService decimalTableService;
        @PostMapping("/insertDecimalTableData")
        public ResponseEntity<String> insertDecimalTableData(@RequestBody DecimalTableEntity decimalTableEntity) throws IOException {
            decimalTableService.insertDecimalTableData(decimalTableEntity);
            return ResponseEntity.ok("insert");
        }
        //http://127.0.0.1:18888/getDecimalTableDataByDate?Date=2015-10-03
        @GetMapping("/getDecimalTableDataByDate")
        public List<DecimalTableEntity> getDecimalTableDataByDate(@RequestParam("Date") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date) throws IOException {
            return decimalTableService.getDecimalTableDataByDate(date);
        }
        @PutMapping("/updateDecimalTableDataByDate")
        public ResponseEntity<Long> updateDecimalTableDataByDate(@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date,
                                                                 @RequestBody HashMap<String, Object> updateFields
        ){
            HashMap<String, Object> updateFieldsConvertByDecimalTableEntity = GenericEntityConverter.convertToTypedMap(updateFields, DecimalTableEntity.class);
            long updateNum = decimalTableService.updateDecimalTableDataByDate(date, updateFieldsConvertByDecimalTableEntity);
            return ResponseEntity.ok(updateNum);
        }
        @DeleteMapping("/deleteDecimalTableDataByDate")
        public ResponseEntity<Long> deleteDecimalTableDataByDate(@RequestParam("Date") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date){
            long deleteNum = decimalTableService.deleteDecimalTableDataByDate(date);
            return ResponseEntity.ok(deleteNum);
        }
    }

至此,增删改查四个接口定义完成。

  • 插入:POST 请求,URL:127.0.0.1:18888/insertDecimalTableData,JSON 字符串:

    {
        "date": "2015-10-03",
        "decimal32": 32.4,
        "decimal64": 64.406,
        "decimal128": 128.158
    }
  • 查询:GET 请求,URL:127.0.0.1:18888/getDecimalTableDataByDate?Date=2015-10-03

  • 更新:PUT 请求,URL:127.0.0.1:18888/updateDecimalTableDataByDate?date=2015-10-03,JSON 字符串:

    {
        "decimal32": 32.1,
        "decimal64": 64.68,
        "decimal128": 128.15
    }
  • 删除:DELETE 请求,URL:127.0.0.1:18888/deleteDecimalTableDataByDate?Date=2015-10-03

4.2 ArrayVector

4.2.1 DolphinDB 建表

db = database("dfs://example")
t = table(1:0, ["DateValue", "arrayInt", "arrayDouble", "arrayTimeStamp"], [DATE,INT[],DOUBLE[],TIMESTAMP[]])
createPartitionedTable(db, t, "arrayTable", "DateValue",,"DateValue")

4.2.2 arrayVector 类型的读和写

arrayVector 类型需要使用 Mybatis 的 handler 功能来支持读和写。

handler 文件夹:

创建 src/main/java/com/dolphindb/demo/handler/DolphinDBIntArrayTypeHandler.java 类。

package com.dolphindb.demo.handler;
import com.dolphindb.jdbc.DolphinDBArray;
import com.xxdb.data.BasicIntVector;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.sql.*;
public class DolphinDBIntArrayTypeHandler extends BaseTypeHandler<int[]> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, int[] parameter, JdbcType jdbcType) throws SQLException {
        ps.setObject(i, parameter, Types.ARRAY);
    }
    @Override
    public int[] getNullableResult(ResultSet rs, String columnName) throws SQLException {
        // 从ResultSet获取BasicIntVector并转为int[]
        DolphinDBArray vector = (DolphinDBArray) rs.getObject(columnName);
        return vector != null ? ((BasicIntVector) vector.getVector()).getdataArray(): null; // 使用getdataArray()方法
    }
    @Override
    public int[] getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        DolphinDBArray vector = (DolphinDBArray) rs.getObject(columnIndex);
        return vector != null ? ((BasicIntVector) vector.getVector()).getdataArray(): null;
    }
    @Override
    public int[] getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        DolphinDBArray vector = (DolphinDBArray) cs.getObject(columnIndex);
        return vector != null ? ((BasicIntVector) vector.getVector()).getdataArray(): null;
    }
}

创建 src/main/java/com/dolphindb/demo/handler/DolphinDBDoubleArrayTypeHandler.java 类。

package com.dolphindb.demo.handler;
import com.dolphindb.jdbc.DolphinDBArray;
import com.xxdb.data.BasicDoubleVector;
import com.xxdb.data.BasicIntVector;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.sql.*;
public class DolphinDBDoubleArrayTypeHandler extends BaseTypeHandler<double[]> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, double[] parameter, JdbcType jdbcType) throws SQLException {
        ps.setObject(i, parameter, Types.ARRAY);
    }
    @Override
    public double[] getNullableResult(ResultSet rs, String columnName) throws SQLException {
        DolphinDBArray vector = (DolphinDBArray) rs.getObject(columnName);
        return vector != null ? ((BasicDoubleVector) vector.getVector()).getdataArray(): null; // 使用getdataArray()方法
    }
    @Override
    public double[] getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        DolphinDBArray vector = (DolphinDBArray) rs.getObject(columnIndex);
        return vector != null ? ((BasicDoubleVector) vector.getVector()).getdataArray(): null;
    }
    @Override
    public double[] getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        DolphinDBArray vector = (DolphinDBArray) cs.getObject(columnIndex);
        return vector != null ? ((BasicDoubleVector) vector.getVector()).getdataArray(): null;
    }
}

创建 src/main/java/com/dolphindb/demo/handler/DolphinDBTimeStampArrayTypeHandler.java 类。

package com.dolphindb.demo.handler;
import com.dolphindb.jdbc.DolphinDBArray;
import com.xxdb.data.BasicTimestampVector;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.sql.*;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
public class DolphinDBTimeStampArrayTypeHandler extends BaseTypeHandler<LocalDateTime[]> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, LocalDateTime[] parameter, JdbcType jdbcType) throws SQLException {
        ps.setObject(i, parameter, Types.ARRAY);
    }
    @Override
    public LocalDateTime[] getNullableResult(ResultSet rs, String columnName)
            throws SQLException {
        DolphinDBArray vector = (DolphinDBArray) rs.getObject(columnName);
        return convertToLocalDateTimeArray(vector);
    }
    @Override
    public LocalDateTime[] getNullableResult(ResultSet rs, int columnIndex)
            throws SQLException {
        DolphinDBArray vector = (DolphinDBArray) rs.getObject(columnIndex);
        return convertToLocalDateTimeArray(vector);
    }
    @Override
    public LocalDateTime[] getNullableResult(CallableStatement cs, int columnIndex)
            throws SQLException {
        DolphinDBArray vector = (DolphinDBArray) cs.getObject(columnIndex);
        return convertToLocalDateTimeArray(vector);
    }
    private LocalDateTime[] convertToLocalDateTimeArray(DolphinDBArray vector) {
        if (vector == null) {
            return null;
        }
        long[] timestamps = ((BasicTimestampVector) vector.getVector()).getdataArray();
        LocalDateTime[] result = new LocalDateTime[timestamps.length];
        for (int i = 0; i < timestamps.length; i++) {
            result[i] = parseTimestamp(timestamps[i]);
        }
        return result;
    }
    public static long countMilliseconds(LocalDateTime dt) {
        // 实现将LocalDateTime转换为毫秒时间戳的逻辑
        return dt.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
    }
    public static LocalDateTime parseTimestamp(long timestamp) {
        // 实现将毫秒时间戳转换为LocalDateTime的逻辑
        return LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.systemDefault());
    }
}

更改 application.properties 配置文件,添加配置:

mybatis.type-handlers-package=com.dolphindb.demo.handler

添加 type-handlers 后,这样就支持了 int[],double[],timestamp[] 类型。

  • entity 文件夹:

    创建 src/main/java/com/dolphindb/demo/entity/ArrayTableEntity.java 类。

    package com.dolphindb.demo.entity;
    import lombok.Data;
    import java.time.LocalDate;
    import java.time.LocalDateTime;
    @Data
    public class ArrayTableEntity {
        private LocalDate dateValue;
        private int[] arrayInt;
        private double[] arrayDouble;
        private LocalDateTime[] arrayTimeStamp;
    }
  • dao 文件夹:

    创建 src/main/java/com/dolphindb/demo/dao/ArrayTableDao.java 接口。

    package com.dolphindb.demo.dao;
    import com.dolphindb.demo.entity.ArrayTableEntity;
    import org.apache.ibatis.annotations.Insert;
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Select;
    import org.apache.ibatis.annotations.Update;
    import java.time.LocalDate;
    import java.time.LocalDateTime;
    import java.util.List;
    @Mapper
    public interface ArrayTableDao {
        @Insert("INSERT INTO arrayTable(dateValue, arrayInt, arrayDouble, arrayTimeStamp) " +
                "VALUES (#{dateValue}, #{arrayInt}, #{arrayDouble}, #{arrayTimeStamp})")
        void insertArrayTableData(ArrayTableEntity arrayTableEntity);
        @Select("SELECT * FROM arrayTable WHERE DateValue = #{date}")
        List<ArrayTableEntity> getArrayTableDataByDate(LocalDate date);
        @Update("UPDATE arrayTable SET arrayInt = #{arrayInt}, arrayDouble = #{arrayDouble} WHERE DateValue = #{date}")
        long updateArrayTableDataByDate(LocalDate date, int[] arrayInt, double[] arrayDouble);
    }

    可以使用 @Insert@Select 等注解,这样就可以省略 mapper 文件。

  • service 文件夹

    新建 src/main/java/com/dolphindb/demo/service/ArrayTableService.java 接口。

    package com.dolphindb.demo.service;
    import com.dolphindb.demo.entity.ArrayTableEntity;
    import java.time.LocalDate;
    import java.time.LocalDateTime;
    import java.util.List;
    public interface ArrayTableService {
        void insertArrayTableData(ArrayTableEntity arrayTableEntity);
        List<ArrayTableEntity> getArrayTableDataByDate(LocalDate date);
        long updateArrayTableDataByDate(LocalDate date, int[] arrayInt, double[] arrayDouble);
    }

    新建 src/main/java/com/dolphindb/demo/service/impl/ArrayTableServiceImpl.java 类。

    package com.dolphindb.demo.service.impl;
    import com.dolphindb.demo.dao.ArrayTableDao;
    import com.dolphindb.demo.entity.ArrayTableEntity;
    import com.dolphindb.demo.service.ArrayTableService;
    import jakarta.annotation.Resource;
    import org.springframework.stereotype.Service;
    import java.time.LocalDate;
    import java.time.LocalDateTime;
    import java.util.List;
    @Service
    public class ArrayTableServiceImpl implements ArrayTableService {
        @Resource
        ArrayTableDao arrayTableDao;
        @Override
        public void insertArrayTableData(ArrayTableEntity arrayTableEntity) {
            arrayTableDao.insertArrayTableData(arrayTableEntity);
        }
        @Override
        public List<ArrayTableEntity> getArrayTableDataByDate(LocalDate date) {
            return arrayTableDao.getArrayTableDataByDate(date);
        }
        @Override
        public long updateArrayTableDataByDate(LocalDate date, int[] arrayInt, double[] arrayDouble) {
            return arrayTableDao.updateArrayTableDataByDate(date, arrayInt, arrayDouble);
        }
    }
  • controller 文件夹:

    新建 src/main/java/com/dolphindb/demo/controller/ArrayTableController.java 类。

    package com.dolphindb.demo.controller;
    import com.dolphindb.demo.entity.ArrayTableEntity;
    import com.dolphindb.demo.service.ArrayTableService;
    import jakarta.annotation.Resource;
    import org.springframework.format.annotation.DateTimeFormat;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.*;
    import java.time.LocalDate;
    import java.util.List;
    @RestController
    public class ArrayTableController {
        @Resource
        ArrayTableService arrayTableService;
        @PostMapping("/insertArrayTableData")
        public ResponseEntity<String> insertArrayTableData(@RequestBody ArrayTableEntity arrayTableEntity){
            arrayTableService.insertArrayTableData(arrayTableEntity);
            return ResponseEntity.ok("insert");
        }
        @GetMapping("/getArrayTableDataByDate")
        public List<ArrayTableEntity> getArrayTableDataByDate(@RequestParam("Date") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date){
            return arrayTableService.getArrayTableDataByDate(date);
        }
        @PutMapping("/updateArrayTableDataByDate")
        public ResponseEntity<Long> updateArrayTableDataByDate(@RequestParam("Date") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date,
                                                               @RequestBody ArrayTableEntity arrayTableEntity){
            long updateNum = arrayTableService.updateArrayTableDataByDate(date, arrayTableEntity.getArrayInt(), arrayTableEntity.getArrayDouble());
            return ResponseEntity.ok(updateNum);
        }
    }

至此,我们实现了 arrayVector 类型的查询、写入和更新三个接口。

写入:POST 请求,URL:127.0.0.1:18888/insertArrayTableData,JSON 字符串:

{
    "dateValue": "2015-10-03",
    "arrayInt": [1,2,3],
    "arrayDouble": [1.2,3.4,5.6],
    "arrayTimeStamp": ["2000-06-16T07:05:29", "2000-06-18T07:05:29", "2000-06-19T07:05:29"]
}

查询:GET 请求,URL:127.0.0.1:18888/getArrayTableDataByDate?Date=2015-10-03

更新:PUT 请求,URL:127.0.0.1:18888/updateArrayTableDataByDate?Date=2015-10-03,JSON 字符串:

{
    "arrayInt": [1,2,3,4],
    "arrayDouble": [1.2,3.4,5.6,7.8]
}

5. 总结

本文成功构建了一个基于 Spring Boot 框架、整合 MyBatis 操作 DolphinDB 数据库的微服务示例。通过系统的开发实践,我们实现了以下核心成果:

  • 完整的开发框架与流程

    • 从零开始搭建了标准的 Spring Boot 项目结构,包括 MVC 分层架构(Controller、Service、Dao、Mapper)。

    • 配置了 DolphinDB JDBC 连接,确保应用能够与 DolphinDB 数据库正常通信。

    • 实现了对 DolphinDB 分布式表的基本操作,包括数据的插入、查询、更新和删除。

  • 全面的数据类型支持

    • 基础类型:完整支持 DolphinDB 的 BOOL、CHAR、SHORT、INT、LONG、DATE、TIME、SECOND、DATETIME、TIMESTAMP、NANOTIME、NANOTIMESTAMP、MINUTE、FLOAT、DOUBLE、SYMBOL、STRING、DATEHOUR、BLOB 等基础数据类型的 CRUD 操作。

    • 特殊类型处理

      • Decimal 类型:针对 DolphinDB 的高精度 DECIMAL 类型,采用 String 类型在 Java 层映射,实现与数据库的准确交互。

      • ArrayVector 类型:通过自定义 MyBatis Type Handler(DolphinDBIntArrayTypeHandlerDolphinDBDoubleArrayTypeHandlerDolphinDBTimeStampArrayTypeHandler),解决数组类型在 JDBC 层与 Java 对象间的转换,实现对 INT[]、DOUBLE[]、TIMESTAMP[] 等数组类型的完整支持。

  • 高级功能实现

    • upsert! 操作:实现了 DolphinDB 特有的 upsert! 功能,支持“存在则更新,不存在则插入”的批量数据处理模式,提高了数据操作的灵活性。

    • 动态类型转换:开发了 GenericEntityConverter 工具类,自动处理 HTTP 请求中 JSON 数据到实体类对象的类型转换,增强了接口的健壮性。

    • 灵活查询:支持多种查询条件,包括等值查询、范围查询(BETWEEN)和 IN 查询,满足了不同的业务场景需求。