搜索 K
Appearance
Appearance
数据建模是 forge 框架的地基:你只写一个带注解的 Java 类,框架在启动期把它解析成结构化定义、推导数据库表结构、落库系统元数据,并在运行期提供统一的 CRUD 门面与关系字段读写能力。这一层的核心设计目标是——让模型类成为唯一事实源(single source of truth),其余产物(表结构、列类型、系统表记录、关系查询)全部由它派生,从而消除「实体类」与「建表脚本」「DAO」「DTO」之间的漂移。
本设计专题按数据流向拆成若干篇。下文先给出全景图与阅读路线,再逐篇说明「它解决什么问题」。如果你更关心怎么写代码,请移步对应的 使用指南。
一条最短链路是这样的:你写好 Pet、Category、Owner 等模型类,应用启动时框架扫描到它们,用反射把 @Model 与 @Field 上的声明翻译成 ModelDefinition 和 FieldDefinition,存进 ModelRegistry;随后 DDL 链路据此建表,SystemMetaManager 把模型、字段写进系统表,最后控制台打印出类似下面这行:
[lifecycle] Lifecycle=DDL, 模块 3 个, 扫描模型 12 个, 耗时 412ms而在运行期,所有读写都收口到 Models 这一个静态门面,关系字段(category/owner/profile/tags)的填充与维护则由两个 Relation API 承担。
建议顺序
首次阅读按下表从上往下,能完整走通「定义 → 解析 → 落库 → 运行期读写」的链路。其中关系查询机制(★)是本专题的核心,建议重点精读。
| 篇目 | 它解决什么问题 | 设计篇 | 使用指南 |
|---|---|---|---|
| 模型与字段注解 | 用 @Model/@Field 声明模型,字段如何映射到列、类型、长度、精度 | 注解与定义模型 | 快速建模 |
| 元数据模型 | ModelDefinition、FieldDefinition、IndexDefinition 承载哪些信息,为何要中立于具体数据库 | 元数据模型 | — |
| 扫描与解析 | 启动期如何发现模型类、用反射构建定义、做 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/onUpdate(CascadeType)当前仅为声明占位,未实现实际级联行为;复合关联键暂未支持。设计篇会在相应位置标注边界,使用时请勿依赖未实现的语义。