对于数据中台描述,我需要将情况讲清楚。
从一个具体的 Java 的 Data Object 对象的实现层面。
启动流程
qData 使用的是 Spring Boot 的框架。
在实现上,数据服务层的抽象使用 ApplicationRunner 接口实现,也就是说, DS API 的持续服务提供功能的初始启动状态是作为组件启动的。
具体情况中,我们的 qdata-module-ds 微服务启动时,位于 package tech.qiantong.qdata.module.ds.config.api 处的 StartedUpRunner 类实现了 ApplicationRunner 接口的类 StartedUpRunner ,也因此在主启动类(应用程序)顺利启动后,逻辑上它的 public void run(ApplicationArguments args) 函数会立即执行。
在 run() 函数中,可以看到核心逻辑。
- 首先,检查 ConfigurableApplicationContext 的配置
- 接着,使用 Service 对象实例的查询方法 lambdaQuery,其实现中调用 BaseMapper 和 BaseEntity 调用 DsApiDO 对象实现对数据库的读取,.eq() 筛选逻辑,再通过 .List(T) 以集合的形式返回,成为我们可使用的 Java 对象。
- 接着,如果列表中存在任意一条 api 对象数据,调用 mapperHandlerMapping 的 registerMapping(api) 方法进行路由注册。再经过一轮逻辑检验,最终会调用 Spring Boot 中的 requestMappingHandlerMapping 类的 registerMapping(requestMapping, handler, method) 方法进行注册。
维护的数据服务层 API
API 服务 DO 对象 DS_API
设计上,qData 系统将 DO 对象存储
与数据源的连接设计抽象 ExecuteConfig
具体实现
@Data
public class ExecuteConfig implements Serializable {
private static final long serialVersionUID=1L;
@NotBlank(message = "数据源不能为空")
private String sourceId;
@NotNull(message = "配置方式不能为空")
private String apiServiceType;
private String tableId;
private String tableName;
private String dbName;
@Valid
private List<FieldParam> fieldParams;
/**
* 解析SQL
*/
private String sqlText;
}
数据如何从数据源读出
使用了一个自定义的 Wrapper 类。
泛型擦除、反射机制。
实现代码
public interface DsApiMapper extends BaseMapperX<DsApiDO> {
default PageResult<DsApiDO> selectPage(DsApiPageReqVO reqVO) {
// 定义排序的字段(防止 SQL 注入,与数据库字段名称一致)
MPJLambdaWrapper<DsApiDO> lambdaWrapper = new MPJLambdaWrapper();
lambdaWrapper.selectAll(DsApiDO.class)
.select("t2.NAME AS catName")
.leftJoin("ATT_API_CAT t2 on t.CAT_CODE = t2.CODE AND t2.DEL_FLAG = '0'")
.between(reqVO.getParamByKey("beginCreateTime") != null && reqVO.getParamByKey("endCreateTime") != null,
DsApiDO::getCreateTime,
reqVO.getParamByKey("beginCreateTime"),
reqVO.getParamByKey("endCreateTime"))
.like(StringUtils.isNotBlank(reqVO.getName()),DsApiDO::getName, reqVO.getName())
.in(reqVO.getApiIdList() != null && !reqVO.getApiIdList().isEmpty(),DsApiDO::getId,reqVO.getApiIdList())
.in(reqVO.getCatIds() != null && !reqVO.getCatIds().isEmpty(),DsApiDO::getCatId,reqVO.getCatIds())
.likeRight(StringUtils.isNotBlank(reqVO.getCatCode()), DsApiDO::getCatCode, reqVO.getCatCode())
.eq(StringUtils.isNotBlank(reqVO.getStatus()),DsApiDO::getStatus, reqVO.getStatus())
.orderByStr(StringUtils.isNotBlank(reqVO.getOrderByColumn()), StringUtils.equals("asc", reqVO.getIsAsc()), StringUtils.isNotBlank(reqVO.getOrderByColumn()) ? Arrays.asList(reqVO.getOrderByColumn().split(",")) : null);
return selectJoinPage(reqVO, DsApiDO.class, lambdaWrapper);
}
}
方法引用和普通方法调用
可以注意到非常有趣的一个小细节。
...(reqVO.getName(), DsAPIDO::getName)
这个实际上是方法引用和普通方法调用的差异。
方法引用实际上是一种预留,意味着在未来实际发生调用时,会使用该方法,而目前知识传递了方法的⌈引用⌋。目前在这里,DsAPIDO 还是未实例化的。而 reqVO 在实例化之后,这里使用 getName() 方法会进行实际的解析和数据调用。