生成库表
先来说说相对简单的生成数据库表的功能实现。
@Override
public void generateTable(String entityCode) {
// 获取实体配置信息
Entity entity = entityService.getByCode(entityCode);
// 获取模块配置信息
Module module = moduleService.query(entity.getModule());
// 获取模型列表
List<EntityModel> entityModelList = entityModelService.getByEntityId(entity.getId());
// 未找到模型,抛出异常
if (CollectionUtils.isEmpty(entityModelList)) {
throw new CustomException(EntityException.MODEL_NOT_FOUND, entity.getName());
}
// 遍历列表
entityModelList.stream().forEach((entityModel) -> {
// 获取自身标识
String entityModelId = entityModel.getId();
// 获取库表名称
String tableName = generateTableName(entityModel.getCode(), module.getAbbreviation());
// 获取库表备注
String tableComment = entityModel.getName();
// 获取父级模型属性列表
List<EntityModelProperty> parentModelPropertyList = new ArrayList<>();
// 循环向上查找父级模型
while (true) {
entityModel = entityModelService.query(entityModel.getParentModel());
// 获取父级模型属性列表
List<EntityModelProperty> currentPropertyList = entityModelPropertyService.getDatabaseStoreListByEntityModelId(entityModel.getId());
CollectionUtils.addAll(parentModelPropertyList, currentPropertyList.iterator());
// 找到顶层节点标识模型停止
if (entityModel.getId().equals(EntityConfigConstant.ID_MODEL_ID)) {
break;
}
}
;
// 获取实体模型属性列表,非库表存储字段忽略
List<EntityModelProperty> entityModelPropertyList = entityModelPropertyService.getDatabaseStoreListByEntityModelId(entityModelId);
CollectionUtils.addAll(entityModelPropertyList, parentModelPropertyList.iterator());
// 将实体模型属性转换为库表字段信息
List<TableFieldInfo> tableFieldInfoList = convertModelPropertyData(entityModelPropertyList);
entityModelService.createTable(tableName, tableComment, tableFieldInfoList);
});
}
方法中添加了详细的注释,不过与实体配置模块的功能设计密切相关,只看代码不了解背后的设计也难以看清楚,我在这里把重点说一下。
实体配置模块中,实体是一个逻辑概念,一个实体可能包含一个或多个数据模型,而数据模型对应着数据库中的库表。例如,销售订单是一个实体,包括两个数据模型,订单信息和订单明细。一组相关的实体,可以归属到一个模块,模块同样是一个逻辑概念,跟通常说的一个系统由多个功能模块组成中的模块含义是相同的。
在生成库表的时候,我们选择一个或多个实体,点击生成库表操作,平台会根据实体编码进行循环处理。
首先根据实体编码,查询对应的实体配置,以及该实体归属的模块信息。
然后查询该实体下属的所有数据模型,针对数据模型,来生成对应的数据库表。
这里还涉及到一个模型继承与复用的核心设计。
系统预置了四个模型:标识模型(只有标识id属性)、映射模型(继承于标识模型,增加了创建人、创建时间、修改人、修改时间)、业务模型(继承于映射模型,增加了逻辑删除标识位)、流程模型(继承于业务模型,增加了与流程相关的一系列发起人、流程状态等属性)。
通过模型继承,实现了模型的灵活扩展和属性字段复用的目的。
因此在读取实体模型属性配置的时候,需要递归往上找父级模型,只到最顶层标识模型,这样才能完整获取到库表的所有字段。
此外,实体中部分属性标记了非库表存储属性,意味着不需要数据库来生成对应的字段,这部分要排除掉。
接下来,通过convertModelPropertyData方法,将数据模型属性配置,转换为数据库字段,如下:
/**
* 将实体模型属性列表转换为库表字段信息列表
*
* @param entityModelPropertyList 实体模型属性列表
* @return 库表字段信息列表
*/
private List<TableFieldInfo> convertModelPropertyData(List<EntityModelProperty> entityModelPropertyList) {
List<TableFieldInfo> tableFieldInfoList = new ArrayList<>();
entityModelPropertyList.stream().forEach(modelProperty -> {
TableFieldInfo tableFieldInfo = new TableFieldInfo();
// 将驼峰字符串转换为下划线格式
String fieldCode = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, modelProperty.getCode());
tableFieldInfo.setCode(fieldCode);
tableFieldInfo.setName(modelProperty.getName());
tableFieldInfo.setDataType(generateDatabaseDataType(modelProperty));
// 是否可为空处理,实际不再使用
String nullFlag = "";
if (modelProperty.getNullFlag().equals(YesOrNoEnum.NO.name())) {
nullFlag = "not null";
}
tableFieldInfo.setNullFlag(nullFlag);
// 库表默认值设定,实际不再使用
String defaultValue = "";
if (StringUtils.isNotBlank(modelProperty.getDefaultValue())) {
defaultValue = "DEFAULT '" + modelProperty.getDefaultValue() + "'";
}
tableFieldInfo.setDefaultValue(defaultValue);
tableFieldInfoList.add(tableFieldInfo);
});
return tableFieldInfoList;
}
其中TableFieldInfo类是自己定义的一个实体类,用来描述表字段信息,源码如下:
package tech.abc.platform.entityconfig.codegenerator.entity;
import lombok.Data;
/**
* 表字段信息
* @author wqliu
* @date 2022-11-8
*/
@Data
public class TableFieldInfo {
/**
* 编码
*/
private String code;
/**
* 名称
*/
private String name;
/**
* 数据类型
*/
private String dataType;
/**
* 是否可为空 YES 是 NO 否
*/
private String nullFlag;
/**
* 默认值
*/
private String defaultValue;
}
数据类型使用了一个辅助处理方法,将实体配置中约定的类型字符串,对应到MySql数据库的数据类型上,源码如下:
/**
* 生成库表的数据类型
*
* @param modelProperty 模型属性
* @return {@link String}
*/
private String generateDatabaseDataType(EntityModelProperty modelProperty) {
String result = StringUtils.EMPTY;
Integer maxLength = modelProperty.getMaxLength();
Integer decimalLength = modelProperty.getDecimalLength();
EntityModelPropertyTypeEnum dataType = EnumUtils.getEnum(EntityModelPropertyTypeEnum.class, modelProperty.getDataType());
switch (dataType) {
case STRING:
result = "VARCHAR(" + maxLength + ")";
break;
case INTEGER:
result = "INT";
break;
case LONG:
result = "BIGINT";
break;
case DOUBLE:
result = "DOUBLE";
break;
case DATETIME:
result = "DATETIME";
break;
case DECIMAL:
result = "DECIMAL(" + maxLength + "," + decimalLength + ")";
break;
case TEXT:
result = "TEXT";
break;
case TIME:
result = "VARCHAR(10)";
break;
default:
result = "VARCHAR(" + maxLength + ")";
}
return result;
}
最后,调用实体数据模型服务的创建表的方法,来生成数据库表,如下:
public void createTable(String tableName, String tableComment, List tableFieldInfoList) {
this.baseMapper.createTable(tableName, tableComment, tableFieldInfoList);
}
<?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="tech.abc.platform.entityconfig.mapper.EntityModelMapper">
<!--创建表-->
<update id="createTable" statementType="STATEMENT">
-- TODO 测试方便,每次生成前都删除库表,正式启用后去除删除,避免数据丢失
DROP TABLE IF EXISTS `${tableName}`;
CREATE TABLE `${tableName}`
(
<foreach collection="tableFieldInfoList" separator="," item="item">
`${item.code}` ${item.dataType} COMMENT '${item.name}'
</foreach>
,PRIMARY KEY (`id`)
)
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT = '${tableComment}';
</update>
</mapper>
注意,这里最开始的时候还有是否为空和默认值的处理,后来去除了,将相关控制放在程序的逻辑层里处理,不在数据库层面进行处理。
开发平台资料
平台名称:一二三应用开发平台
简介: 企业级通用低代码应用开发平台,免费全开源可商用
设计资料:csdn专栏
开源地址:Gitee
开源协议:MIT
如果您在阅读本文时获得了帮助或受到了启发,我衷心希望您能够喜欢并收藏这篇文章,为它点赞,并在评论区与我分享您的想法和心得。让我们一起交流学习,不断进步,遇见更加优秀的自己!