Skip to content

关联关系

ACE ORM 支持四种标准关联关系,全部通过编译期宏声明,运行时零反射。

@ManyToOne — 多对一

多方持有外键列,指向一方的主键。

cangjie
@Entity["comments"]
public class Comment {
    @Id[]
    public var id: Int64 = 0

    @Column[]
    public var body: String = ""

    // 外键列 author_id 自动生成,关联到 users.id
    @ManyToOne["users"]
    public var author: ?User = None
}

@ManyToOne 参数:

语法说明
@ManyToOne["table"]关联表名,FK 列名 = fieldName + "Id"(如 authorId
@ManyToOne["table", "fk_col"]自定义 FK 列名
@ManyToOne["table", "fk_col", "ref_id"]自定义 FK 列名与被引用列名
onDelete: "CASCADE"DDL ON DELETE 动作
onUpdate: "SET NULL"DDL ON UPDATE 动作
cangjie
@ManyToOne["users", "author_id", onDelete: "CASCADE"]
public var author: ?User = None

Int64 外键写法(仅存 id,不加载对象)

cangjie
@ManyToOne["departments"]
public var departmentId: Int64 = 0    // 字段类型 Int64 时仅生成 FK 列,不生成关系导航

@OneToMany — 一对多(集合端)

在"一"方声明集合,不持有外键(外键在多方):

cangjie
@Entity["users"]
public class User {
    @Id[]
    public var id: Int64 = 0

    @Column[]
    public var name: String = ""

    // 声明集合,无 FK 列(FK 在 Comment.authorId)
    @OneToMany["comments", "authorId"]
    public var comments: Array<Comment> = []
}

@OneToMany["table", "fk_field"] 参数:

位置说明
第一参数多方表名
第二参数多方实体中外键字段名(字段名,非列名)

集合默认不加载

findById/findAll 等方法不自动加载 @OneToMany 集合,需显式调用 loadCollection 或用 findOne(id, ["comments"])


@OneToOne — 一对一

Owner 侧(持外键)

cangjie
@Entity["users"]
public class User {
    @Id[]
    public var id: Int64 = 0

    @OneToOne["profiles"]
    public var profile: ?Profile = None    // 生成 profileId 外键列
}

Inverse 侧(无外键,双向导航)

cangjie
@Entity["profiles"]
public class Profile {
    @Id[]
    public var id: Int64 = 0

    @Column[]
    public var bio: String = ""

    @OneToOne["users", inverseBy: "profileId"]
    public var user: ?User = None    // 通过 users.profileId 反向导航
}

@ManyToMany — 多对多

需要配合 @JoinTable 在 owner 侧声明中间表:

cangjie
@Entity["posts"]
public class Post {
    @Id[]
    public var id: Int64 = 0

    @Column[]
    public var title: String = ""

    @ManyToMany["tags"]
    @JoinTable["post_tags", "postId", "tagId"]
    public var tags: Array<Tag> = []
}

@Entity["tags"]
public class Tag {
    @Id[]
    public var id: Int64 = 0

    @Column[]
    public var name: String = ""

    // 反向侧(不声明 @JoinTable)
    @ManyToMany["posts"]
    public var posts: Array<Post> = []
}

@JoinTable["join_table", "owner_fk", "inverse_fk"]

参数说明
"post_tags"中间表名
"postId"指向 owner 的外键列
"tagId"指向 inverse 的外键列

@RelationId — 只读外键 ID 字段

在不需要对象导航时,只读取关联 ID:

cangjie
@Entity["comments"]
public class Comment {
    @Id[]
    public var id: Int64 = 0

    @ManyToOne["users"]
    public var author: ?User = None

    @RelationId["author"]       // 反映 authorId 列的只读视图
    public var authorId: Int64 = 0
}

加载关联数据

findOne — 单次 LEFT JOIN 加载

cangjie
// 加载 comment 同时 LEFT JOIN 加载 author
match (repo.findOne(DbInt(1), ["author"])) {
    case Some(comment) =>
        match (comment.author) {
            case Some(user) => println(user.name)
            case None => println("no author")
        }
    case None => ()
}

多个关联一次加载:

cangjie
match (postRepo.findOne(DbInt(42), ["author", "category"])) {
    case Some(post) => ...
    case None => ()
}

loadRelations — 懒加载对象关联

cangjie
let comment = commentRepo.findById(DbInt(1)).getOrThrow()
commentRepo.loadRelations(comment, ["author"])   // 回填 comment.author

loadCollection — 懒加载集合

cangjie
let user = userRepo.findById(DbInt(10)).getOrThrow()
userRepo.loadCollection(user, "comments")   // 回填 user.comments
println(user.comments.size)

集合管理(多对多)

addRelation — 添加关联

cangjie
// 给 post 添加 tag(在中间表插入一行)
postRepo.addRelation(post, "tags", DbInt(tagId))

removeRelation — 移除关联

cangjie
// 解除 post 与 tag 的关联
postRepo.removeRelation(post, "tags", DbInt(tagId))

countRelation — 统计关联数

cangjie
let tagCount = postRepo.countRelation(post, "tags")

leftJoinAndSelect / innerJoinAndSelect(QueryBuilder)

在 QueryBuilder 中按关联字段名 JOIN 并自动回填对象图:

cangjie
let posts = postRepo
    .createQueryBuilder("p")
    .leftJoinAndSelect("author", "u")       // 字段名 "author" → JOIN users u
    .innerJoinAndSelect("category", "cat")
    .andWhere("p.published", "=", DbBool(true))
    .orderBy("p.createdAt", "DESC")
    .limit(10)
    .getMany()

for (p in posts) {
    match (p.author) {
        case Some(u) => println("${p.title} by ${u.name}")
        case None => ()
    }
}

完整示例:博客

cangjie
package blog.model

import ace_orm.*
import ace_orm.macros.*

@Entity["users"]
public class User {
    @Id[]
    public var id: Int64 = 0

    @Column[nullable: false, unique: true]
    public var email: String = ""

    @Column[]
    public var name: String = ""

    @OneToMany["posts", "authorId"]
    public var posts: Array<Post> = []
}

@Entity["categories"]
public class Category {
    @Id[]
    public var id: Int64 = 0
    @Column[]
    public var name: String = ""
}

@Entity["posts"]
public class Post {
    @Id[]
    public var id: Int64 = 0

    @Column[]
    public var title: String = ""

    @ManyToOne["users", "author_id"]
    public var author: ?User = None

    @ManyToOne["categories"]
    public var category: ?Category = None

    @ManyToMany["tags"]
    @JoinTable["post_tags", "postId", "tagId"]
    public var tags: Array<Tag> = []

    @CreateDateColumn[]
    public var createdAt: Int64 = 0
}

@Entity["tags"]
public class Tag {
    @Id[]
    public var id: Int64 = 0
    @Column[]
    public var name: String = ""
}

基于 Apache-2.0 许可证发布