TSts-grm

关联

关联把表与表连起来。四种方向,一句话记住谁拥有外键

关联外键位置本模型视角
m2o(多对一)本表(拥有方)带一个关联对象(store
o2m(一对多)对方表带一个集合(books
o2o(一对一)其中一侧(拥有方)带一个关联对象
m2m(多对多)中间表带一个集合(authors

多对一与一对多:同一张外键

// ① 书与书店:多对一在 Book(外键 STORE_ID),一对多在 BookStore(纯反向)
const BookStore = model("BookStore", "id", class {
    id = prop.i64()
    name = prop.str(100)
    books = prop.o2m(Book).mappedBy("store")
        .orderBy("name", "edition")      // 集合默认排序(取 books 时的默认顺序)
})

const Book = model("Book", "id", class {
    id = prop.i64()
    name = prop.str(50)
    edition = prop.i32()
    price = prop.num(10, 2)

    // 拥有方:生成外键列 STORE_ID;.nullable() = 外键可空
    store = prop.m2o(BookStore)
        .joinColumns({ cascade: "DELETE" })   // 删书店级联删书
        .nullable()
})
  • o2m 永远通过 mappedBy 指向对方某个 m2o/o2o 属性名,本身不产生列;
  • joinColumns({ cascade: "DELETE" }) 声明级联:删书店时删掉它的书(级联方向由拥有方决定);
  • o2m(...).orderBy(...) 给集合定默认排序,查询取 books 时自动带上。

多对多:中间表

m2m 的常见形态是用中间表连接两侧(示例数据已含 author / book_author_mapping 表与数据,此例可运行,完整版本见工作区):

// 书 ↔ 作者:book_author_mapping 中间表(片段:完整可运行版见工作区)
const AUTHOR = model("Author", "id", class {
    id = prop.i64()
    name = prop.str(50)
    books = prop.m2m(BOOK).mappedBy("authors")   // 反向:不必成对声明,方便时再加
})

const BOOK = model("Book", "id", class {
    id = prop.i64()
    name = prop.str(50)
    // 拥有侧:声明中间表与两侧的外键列
    authors = prop.m2m(AUTHOR).joinTable({
        name: "book_author_mapping",
        joinThisColumns: ["book_id"],      // 中间表里指向"本侧"的列
        joinTargetColumns: ["author_id"],  // 中间表里指向"对侧"的列
    })
})
  • 默认命名策略下中间表名、列名可省略(自动推导);
  • 反向 m2m(...).mappedBy("authors") 与 o2m 同理,不建任何列;
  • ⚠️ 已知限制(0.0.10):m2m 集合的子视图取 embedded 子路径(如 c.author.name.lastName)会触发 DTO 构造错误——先取整个 embedded(c.author.name)或标量;随上游修复跟进。

多对多带载荷:joinEntity

中间表还想带自己的字段(如选课成绩、关联时间)?用显式中间实体

const COURSE = model("Course", "id", class {
    id = prop.i64()
    name = prop.str(50)
    // students 经由 LearningLink 实体连接,成绩等字段在链上
    students = prop.m2m(STUDENT).joinEntity({
        model: LEARNING_LINK,           // 中间实体(自带 id/score/student/course)
        joinThisProp: "student",        // 中间实体指向本侧的 m2o 属性
        joinTargetProp: "course",       // 中间实体指向对侧的 m2o 属性
    })
})

此时 LearningLink 是一个普通实体:既能按关联用,也能直接查它的 score 等载荷字段。

一对一

o2o 语义与 m2o 相同,只是外键加了唯一约束(一侧只能有一个)。涉及它时请以当前版本 API 为准。

不对称重申

m2o 与 o2m 不需要成对出现(「快速上手 → 视图」讲过):外键建在哪张表,关联就在哪个模型声明;反向只是便利视角。m2m 同理——只在真正需要"从另一侧出发"时再补 mappedBy

下一步

关联之外,模型体系还有两块大内容:继承与多态(同一个概念多种形态)与复合值(一列变成一组列)。先看继承。