Skip to content

数据建模 · 设计总览

数据建模是 forge 框架的地基:你只写一个带注解的 Java 类,框架在启动期把它解析成结构化定义、推导数据库表结构、落库系统元数据,并在运行期提供统一的 CRUD 门面与关系字段读写能力。这一层的核心设计目标是——让模型类成为唯一事实源(single source of truth),其余产物(表结构、列类型、系统表记录、关系查询)全部由它派生,从而消除「实体类」与「建表脚本」「DAO」「DTO」之间的漂移。

本设计专题按数据流向拆成若干篇。下文先给出全景图与阅读路线,再逐篇说明「它解决什么问题」。如果你更关心怎么写代码,请移步对应的 使用指南

全景:从一个类到一行日志

一条最短链路是这样的:你写好 PetCategoryOwner 等模型类,应用启动时框架扫描到它们,用反射把 @Model@Field 上的声明翻译成 ModelDefinitionFieldDefinition,存进 ModelRegistry;随后 DDL 链路据此建表,SystemMetaManager 把模型、字段写进系统表,最后控制台打印出类似下面这行:

text
[lifecycle] Lifecycle=DDL, 模块 3 个, 扫描模型 12 个, 耗时 412ms

而在运行期,所有读写都收口到 Models 这一个静态门面,关系字段(category/owner/profile/tags)的填充与维护则由两个 Relation API 承担。

阅读路线

建议顺序

首次阅读按下表从上往下,能完整走通「定义 → 解析 → 落库 → 运行期读写」的链路。其中关系查询机制(★)是本专题的核心,建议重点精读。

篇目它解决什么问题设计篇使用指南
模型与字段注解@Model/@Field 声明模型,字段如何映射到列、类型、长度、精度注解与定义模型快速建模
元数据模型ModelDefinitionFieldDefinitionIndexDefinition 承载哪些信息,为何要中立于具体数据库元数据模型
扫描与解析启动期如何发现模型类、用反射构建定义、做 fail-fast 校验扫描与解析模块与扫描
关系查询机制 ★O2O/M2O/O2M/M2M 四类关系的读填充如何用批量 IN 查询避免 N+1关系读填充查询关系字段
关系写维护保存时如何回填外键、如何对中间表做 diff 增删关系写维护保存关系字段
元数据落库模型/字段为何要写进 sys_model/sys_field,消失项如何处理元数据落库

各篇要点速览

模型与字段注解

回答「怎么把一个普通 Java 类变成框架认识的模型」。@Model 给出模型名、显示名与高级配置(表名、逻辑删除、索引);@Field 配合类型子注解(@Field.String@Field.BigDecimal@Field.Enum 等)描述每个字段如何落列。关键设计点是「显式优于隐式」:列名、表名可省略由驼峰转下划线推导,但纯应用层字段必须显式 store=false,落库枚举必须实现 ValueEnum<T>。详见 注解与定义模型

元数据模型

回答「定义信息存在哪、长什么样」。ModelDefinition 持有模型级信息(表名、模型类型、逻辑删除生效值、索引列表),FieldDefinition 持有字段级信息(列名、Java 类型、FieldType、长度精度、枚举存储类型、关系配置)。这一层刻意做成运行时中立的数据结构,不绑定任何数据库方言——方言只在最后一刻通过 dialect.mapColumnType(field)FieldDefinition 翻译成具体列类型。详见 元数据模型

扫描与解析

回答「启动时框架怎么找到并解析这些类」。ModelParser 在包扫描命中后用反射读取注解、构建定义、注册进 ModelRegistry,并在此阶段做 fail-fast 校验(如枚举未实现 ValueEnum 直接抛异常、复合关联键明确报错)。这与启动钩子、自动装配相关,可参阅 启动钩子与包扫描前置知识。详见 扫描与解析

关系查询机制 ★

回答本专题最核心的问题:四类关系字段在读取时如何高效填充。设计上把 M2O/O2O 归为「to-one」、O2M 归为「对端持外键」、M2M 归为「中间表中转」,三类处理都走批量 IN 查询 + 内存建索引的套路,从根上规避 N+1。

详见 关系读填充,配套写法见 查询关系字段

关系写维护

回答「保存时关系字段怎么落库」。Models.saveWith 把一次保存拆成三步:落库前 applyToOne 从关系对象回填本表外键、落库本表分配主键、落库后 syncM2M 对中间表按期望集合与现状做 diff 增删。这里有两个刻意的设计取舍:M2O/O2O 支持「放对象」与「放 id」两种形态且对象优先;M2M 集合元素必须是对象(含半对象),不支持裸 id 集合。O2M 的存储维护落在对端外键列上,不在本侧处理。详见 关系写维护,配套写法见 保存关系字段

元数据落库

回答「为什么模型还要再写一份到数据库」。SystemMetaManager 把模块、模型、字段分别写进 SysModule/SysModel/SysField 三张系统表,作为「框架自描述」的持久副本,供后续 DDL 比对、声明式后台管理协议等上层能力消费。落库时按业务键全量比对、记录本轮存活键、删除消失项,并打印耗时统计日志。详见 元数据落库

一以贯之的设计取舍

选项选择理由
实体注解 vs 独立建表脚本注解即定义,表结构派生单一事实源,避免实体与 DDL 漂移
关系逐条查 vs 批量 IN批量 IN + 内存建索引从根上规避 N+1,列表填充只多几条 IN
复合关联键 vs 单字段关联键当前仅支持单字段,复合键 fail-fast 报错控制实现复杂度,先把单键链路打磨稳
M2M 收集裸 id vs 对象集合元素必须为对象(含半对象)M2M 无外键标量列兜底,统一以对象读被引用值
方言耦合进定义 vs 中立定义FieldDefinition 中立,方言最后翻译列类型同一套模型可适配不同数据库
删表删列直接物理删 vs 默认逻辑归档默认逻辑删(_d_ 归档/重命名),allow-drop=true 才物理删误删可恢复,保护线上数据

占位能力须知

关系注解上的级联策略 onDelete/onUpdateCascadeType)当前仅为声明占位,未实现实际级联行为;复合关联键暂未支持。设计篇会在相应位置标注边界,使用时请勿依赖未实现的语义。

读完本总览后,建议从 注解与定义模型 顺序推进,核心精读 关系读填充。需要动手写代码时,直接跳到 数据建模使用指南