关联关系
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 = NoneInt64 外键写法(仅存 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.authorloadCollection — 懒加载集合
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 = ""
}