Skip to Content
文档后端开发模型开发元数据注解

元数据注解

Softa 通过两条并存的路径维护 sys_* 目录表:

  1. 注解(本页)——代码先行(code-first)。在 Java 实体类上声明元数据, 启动时由扫描器读取这些注解,与 sys_* 表对齐,并在开发模式下执行相应 DDL。 适合随代码库交付的基础定义——平台 / 框架模型,与源码一同版本化, 受代码评审和 CI/CD 约束。
  2. Studio(可视化设计器)——配置先行(config-first)。可视化工作台 通过 WorkItem → Version → Deployment 的发布流程写入同样的 sys_* 行。 适合租户自定义与业务团队主导的配置,无需重新发布也能调整。 详见 Studio 使用指南

两条路径不会互相覆盖:每条 sys_* 行都带有 Ownership 标记,注解扫描器只读写 PLATFORM_MAINTAINED 行。

需要 metadata-starter 作为应用的依赖,本节中的注解才会生效。 softa-orm 负责定义注解,metadata-starter 提供读取注解并与 sys_* 目录表对齐的扫描器和校验器。若缺少 metadata-starter,注解虽然仍写在你的类上, 但没有任何扫描器消费它们——sys_* 行不会被写入,也不会生成 DDL。

五个注解,全部位于 io.softa.framework.orm.annotation

注解作用目标写入的 sys_*用途
@Modelsys_model描述一个实体(表名、业务键、多租户、软删除等)
@Field字段sys_field描述一列(标签、类型、长度、是否必填、关联等)
@OptionSet枚举类sys_option_set将枚举标记为受管的选项集
@OptionItem枚举常量sys_option_item选项项级别的展示属性
@Index类(@Repeatablesys_model_index声明数据库索引
@Data @EqualsAndHashCode(callSuper = true) @Model( labelName = "Customer", businessKey = {"code"}, description = "Customer master" ) @Index(name = "uk_customer_code", fields = {"code"}, unique = true) @Index(fields = {"status", "createdTime"}) public class Customer extends AuditableModel { private Long id; @Field(labelName = "Customer Code", required = true, length = 32) private String code; @Field(labelName = "Customer Tier") private CustomerTier tier; // enum → FieldType.OPTION (inferred) } @OptionSet(name = "Customer Tier") public enum CustomerTier { @OptionItem(itemName = "VIP Gold") GOLD("g"), @OptionItem(itemName = "Silver") SILVER("s"); @JsonValue private final String code; // itemCode = @JsonValue CustomerTier(String code) { this.code = code; } }

推断规则(无需注解)

概念推断来源覆盖方式
modelName类的简单名—(不可覆盖)
fieldNameJava 字段名—(不可覆盖)
optionSetCode枚举类的简单名—(不可覆盖)
itemCode@JsonValue 字段值(兜底使用 enum.name()—(不可覆盖)
tableNamesnake_case(modelName)@Model.tableName
columnNamesnake_case(fieldName)@Field.columnName
fieldTypeTypeInference 根据 Java 类型推断(如 StringSTRING,枚举→OPTIONList<enum>MULTI_OPTION@Model POJO→MANY_TO_ONE@Field.fieldType = { ... }(单元素);OPTION / MULTI_OPTION 不能显式书写
索引 indexName非唯一索引为 idx_<table>_<col>...,唯一索引为 uk_<table>_<col>...@Index.name

@ModelSysModel

各属性的业务语义:见 模型元数据

@Model 属性类型默认值SysModel说明
(类的简单名)modelName推断得到,不可覆盖
labelNameString""labelName空 → i18n key model.{modelName}.label
tableNameString""tableName空 → snake_case(modelName)
descriptionString""description
displayNameString[]{}displayName列表展示默认字段
searchNameString[]{}searchName搜索字段默认值
defaultOrderString[]{}defaultOrder"createdTime:desc"
softDeletebooleanfalsesoftDelete
softDeleteFieldString"deleted"softDeleteField仅当 softDelete = true 时生效
activeControlbooleanfalseactiveControl增加 active 启用控制列
timelinebooleanfalsetimeline时效行(见时间线模型)
idStrategyIdStrategyDB_AUTO_IDidStrategy
storageTypeStorageTypeRDBMSstorageType
versionLockbooleanfalseversionLock乐观锁列
multiTenantbooleanfalsemultiTenant要求类上存在 tenantId 字段
dataSourceString""dataSource空 → 主数据源
businessKeyString[]{}businessKey支持复合业务键
partitionFieldString""partitionField
serviceNameString""serviceName微服务路由键 — 参见 softa-web/README
(扫描器写入)appId始终由扫描器 / Studio 写入
(数据库自动)id主键
(扫描器写入)ownership扫描器写入的行固定为 PLATFORM_MAINTAINED

审计字段(createdTime / createdBy / createdId / updatedTime / updatedBy / updatedId)来自 AuditableModel需要通过 @Field 声明——当类继承 AuditableModel 时,DdlGenerator 会自动注入这些列。

@FieldSysField

各属性的业务语义与完整字段类型表:见 字段元数据

@Field 属性类型默认值SysField说明
(Java 字段名)fieldName推断得到,不可覆盖
(Java 类型)fieldTypeTypeInference 推断
labelNameString""labelName空 → 使用 i18n key
descriptionString""description
fieldTypeFieldType[]{}fieldType单元素覆盖;OPTION / MULTI_OPTION 不能显式书写
columnNameString""columnName空 → snake_case(fieldName)
lengthint0length0 → 使用类型默认长度;用于 STRING / DECIMAL 精度
scaleint0scaleDECIMAL 小数位
requiredbooleanfalserequiredNOT NULL 约束
readonlybooleanfalsereadonlyUI 提示
translatablebooleanfalsetranslatable多语言列
nonCopyablebooleanfalsenonCopyable排除在 copy() 之外
unsearchablebooleanfalseunsearchable排除在默认搜索之外
computedbooleanfalsecomputed需要配合 expression
expressionString""expressionAviatorScript 表达式
dynamicbooleanfalsedynamic不进行物理存储
encryptedbooleanfalseencrypted静态加密
maskingTypeMaskingType[]{}maskingType单元素
defaultValueString""defaultValue
relatedModelString""relatedModel空 → 从 POJO 类型推断;当 Java 类型是 Long 用于存外键 id 时必填
relatedFieldString""relatedField空 → "id"
joinModelString""joinModel多对多关联表
joinLeftString""joinLeft
joinRightString""joinRight
cascadedFieldString""cascadedField点号路径,如 "owner.name"
filtersString""filters关联的过滤表达式
widgetTypeWidgetType[]{}widgetType单元素覆盖
(扫描器写入)modelName来自外层 @Model
(扫描器写入)optionSetCodefieldTypeOPTION / MULTI_OPTION 时从枚举类型推断
(扫描器写入)appId / id / ownership
(外键初始化后回填)modelId
(不通过 @Field 暴露)hidden仅 UI 层标记,由 Studio 设置

@OptionSetSysOptionSet

运行时行为、缓存机制与 API 返回形态:见 选项集

@OptionSet 属性类型默认值SysOptionSet说明
(枚举的简单名)optionSetCode推断得到,不可覆盖
nameString""name显示标签;空 → 使用 i18n key
descriptionString""description
(扫描器写入)appId / id / ownership
(Studio 切换)deleted / optionItems运行时聚合

@OptionItemSysOptionItem

展示属性(itemToneitemIcon)与 API 返回形态:见 选项集

@OptionItem 属性类型默认值SysOptionItem说明
(枚举上 @JsonValue 字段的值)itemCode@JsonValue 时兜底使用 enum.name()
(外层枚举的简单名)optionSetCode推断得到
itemNameString""itemName空 → 兜底使用 itemCode
descriptionString""description
sequenceint-1sequence-1 → 使用 ordinal() + 1
parentItemCodeString""parentItemCode层级关系
itemToneOptionItemTone[]{}itemTone单元素
itemIconOptionItemIcon[]{}itemIcon单元素
(扫描器写入)appId / id / ownership / optionSetId
(Studio 切换)active

@IndexSysModelIndex

@Index@Repeatable 的——可以在同一个 @Model 类上叠加多次声明。

@Index 属性类型默认值SysModelIndex说明
(外层类)modelName推断得到
nameString""name显示标题;为空时由字段自动派生
name(或自动派生)indexName非唯一索引 idx_<table>_<col>...,唯一索引 uk_<table>_<col>...
fieldsString[]必填indexFields驼峰式的 Java 字段名,不是列名
uniquebooleanfalseuniqueIndex
(扫描器写入)appId / id / ownership
(外键初始化后回填)modelId

注意@Model.businessKey 不会自动创建唯一索引。 多租户模型通常需要 UNIQUE (tenant_id, businessKey...), 这种带租户语义的索引无法仅通过 @Index 表达——请显式声明:

@Index(fields = {"tenantId", "code"}, unique = true)

行归属(Ownership 枚举)

sys_model / sys_field / sys_option_set / sys_option_item / sys_model_index 中的每一行都带有 ownership 列 (io.softa.framework.orm.enums.Ownership):

取值写入方租户是否可改?
PLATFORM_MAINTAINED扫描器(来自 @Model / @Field / @OptionSet / @OptionItem / @Index
PLATFORM_DEFAULTStudio Open API / DML 种子数据(用于诸如 Language 这类无法承载 @OptionSet 的框架枚举)✅ 可逐行覆盖
TENANT(默认)Studio 界面 / Open API

扫描器的读写都带有 WHERE ownership = 'PLATFORM_MAINTAINED' 过滤条件, 因此平台默认值与租户的自定义内容不会被一次注解对齐操作覆盖。

完整的合并规则契约见 Ownership.java 的 javadoc。

最后更新于