实体 @Entity
ACE ORM 采用编译期宏路线:@Entity 在构建阶段生成 Mapper 与 Repository,运行时零反射开销。
支持的数据库
| 数据库 | driver 值 | 说明 |
|---|---|---|
| SQLite | sqlite | 嵌入式,无需服务 |
| PostgreSQL | postgres | 推荐生产使用 |
| MySQL | mysql | 兼容 MariaDB |
配置数据源(ace.toml):
toml
[datasource]
driver = "sqlite"
url = "app.db"多驱动需在 link-option 中链接对应客户端库:
toml
# ace-orm/cjpm.toml 或可执行模块
link-option = "-lsqlite3 -lpq -lmysqlclient"@Entity — 映射数据库表
cangjie
package demo.model
import ace_orm.*
import ace_orm.macros.*
@Entity["users"]
public class User {
@Id[]
public var id: Int64 = 0
@Column[]
public var name: String = ""
@Column[]
public var email: String = ""
}| 参数 | 说明 |
|---|---|
@Entity["table_name"] | 指定表名(必填) |
@Entity["table_name", naming: "snake"] | 启用字段名→列名 snake_case 转换(opt-in,默认关闭) |
零样板
@Entity 同时生成 UserRepository(具体仓储类)并注册为可注入 Bean——不需要手动继承或注册。
@Id — 自增主键
cangjie
@Id[]
public var id: Int64 = 0- 类型固定为
Int64 - 每个实体有且只有一个
@Id字段 - 数据库列类型:SQLite
INTEGER AUTOINCREMENT,PGBIGSERIAL,MySQLBIGINT AUTO_INCREMENT
自定义列名:
cangjie
@Id["user_id"]
public var id: Int64 = 0@PrimaryColumn — 手动主键(非自增)
不依赖数据库自增,主键值由应用指定:
cangjie
@Entity["orders"]
public class Order {
@PrimaryColumn[]
public var orderNo: String = "" // 自然主键,如业务单号
@Column[]
public var amount: Float64 = 0.0
}WARNING
手动主键 insert 时必须赋值,save 方法依据主键是否为空串判断 insert/update。
@Column — 普通列
基础用法
cangjie
@Column[]
public var title: String = "" // 列名 = 字段名 "title"
@Column["created_at"]
public var createdAt: Int64 = 0 // 列名 = "created_at"完整选项
cangjie
@Column[
nullable: false, // NOT NULL(默认 true = 允许 NULL)
unique: true, // UNIQUE 约束
defaultSql: "''", // DDL 默认值,原样注入 SQL
length: 255, // VARCHAR(255),仅影响 DDL
type: "TEXT", // 覆盖方言自动推断的列类型
autoIncrement: false // 禁止自增(与 @Id 配合用于复合主键)
]
public var email: String = ""| 选项 | 类型 | 默认 | 说明 |
|---|---|---|---|
nullable | Bool | true | false → DDL 追加 NOT NULL |
unique | Bool | false | 生成 UNIQUE 约束 |
defaultSql | String | "" | DDL DEFAULT 值(原始 SQL,如 "CURRENT_TIMESTAMP") |
length | Int64 | 0 | 字符串列长度,0 = 不限 |
type | String | "" | 强制覆盖方言类型(如 "JSONB", "UUID") |
autoIncrement | Bool | false | 同 @Id,通常不单独使用 |
仓颉类型 → 列类型对照
| 仓颉类型 | SQLite | PostgreSQL | MySQL |
|---|---|---|---|
Int64 | INTEGER | BIGINT | BIGINT |
String | TEXT | TEXT | LONGTEXT |
Float64 | REAL | DOUBLE PRECISION | DOUBLE |
Bool | INTEGER | BOOLEAN | TINYINT(1) |
Array<UInt8> | BLOB | BYTEA | LONGBLOB |
@Index — 单列索引
cangjie
@Entity["articles"]
public class Article {
@Id[]
public var id: Int64 = 0
@Index[]
public var slug: String = "" // 普通索引
@Index["idx_art_pub", unique: true]
public var publishedAt: Int64 = 0 // 唯一索引,自定义索引名
}@Index[] 参数:
| 参数 | 说明 |
|---|---|
@Index[] | 普通索引,索引名自动生成(idx_<table>_<col>) |
@Index["name"] | 指定索引名 |
@Index["name", unique: true] | 唯一索引 |
@TableIndex — 多列复合索引
标注在类上:
cangjie
@TableIndex["idx_user_email_role", cols: ["email", "role"]]
@Entity["users"]
public class User {
@Id[]
public var id: Int64 = 0
@Column[]
public var email: String = ""
@Column[]
public var role: String = ""
}审计列
@CreateDateColumn — 创建时间
cangjie
@CreateDateColumn[]
public var createdAt: Int64 = 0 // 毫秒时间戳,insert 时自动填充@UpdateDateColumn — 更新时间
cangjie
@UpdateDateColumn[]
public var updatedAt: Int64 = 0 // 毫秒时间戳,insert/update 时自动填充@DeleteDateColumn — 软删除时间戳
cangjie
@DeleteDateColumn[]
public var deletedAt: ?Int64 = None // NULL = 未删除;非 NULL = 软删除时间戳- 声明后,
findAll/findBy等方法自动追加WHERE deletedAt IS NULL过滤 repo.softDelete(conds)填充时间戳;repo.restore(conds)重置为 NULLrepo.withDeleted()返回不过滤软删除的视图
@VersionColumn — 乐观锁版本号
cangjie
@VersionColumn[]
public var version: Int64 = 0 // 每次 update 自动 +1;受影响行为 0 时抛 OptimisticLockException生命周期钩子
在实体类内定义方法并用钩子宏标注:
cangjie
@Entity["products"]
public class Product {
@Id[]
public var id: Int64 = 0
@Column[]
public var name: String = ""
@Column[]
public var slug: String = ""
@BeforeInsert[]
func generateSlug(): Unit {
slug = name.toLower().replace(" ", "-")
}
@AfterInsert[]
func afterSave(): Unit {
println("Product #${id} saved")
}
@BeforeUpdate[]
func onUpdate(): Unit {
slug = name.toLower().replace(" ", "-")
}
@AfterUpdate[]
func afterUpdate(): Unit {}
@BeforeRemove[]
func onRemove(): Unit {
println("Removing product #${id}")
}
@AfterRemove[]
func afterRemove(): Unit {}
}| 宏 | 触发时机 |
|---|---|
@BeforeInsert[] | insert 写库前 |
@AfterInsert[] | insert 写库后(id 已填充) |
@BeforeUpdate[] | update 写库前 |
@AfterUpdate[] | update 写库后 |
@BeforeRemove[] | remove(e) 写库前 |
@AfterRemove[] | remove(e) 写库后 |
INFO
deleteById / deleteWhere / softDelete 不触发 Remove 钩子(无实体实例),只有 repo.remove(entity) 会触发。
snake_case 命名策略
默认列名 = 字段名(camelCase 不转换)。开启 naming: "snake" 后,字段名自动转 snake_case:
cangjie
@Entity["orders", naming: "snake"]
public class Order {
@Id[]
public var id: Int64 = 0
@Column[]
public var createdAt: Int64 = 0 // 列名 → "created_at"
@Column[]
public var totalAmount: Float64 = 0.0 // 列名 → "total_amount"
}TIP
@Column["alias"] 显式别名优先于 naming 策略。
完整实体示例
cangjie
package demo.model
import ace_orm.*
import ace_orm.macros.*
@Entity["articles", naming: "snake"]
public class Article {
@Id[]
public var id: Int64 = 0
@Column[nullable: false, length: 200]
public var title: String = ""
@Index["idx_art_slug", unique: true]
public var slug: String = ""
@Column[type: "TEXT"]
public var body: String = ""
@Column[defaultSql: "0"]
public var viewCount: Int64 = 0
@CreateDateColumn[]
public var createdAt: Int64 = 0
@UpdateDateColumn[]
public var updatedAt: Int64 = 0
@DeleteDateColumn[]
public var deletedAt: ?Int64 = None
@VersionColumn[]
public var version: Int64 = 0
@BeforeInsert[]
func buildSlug(): Unit {
if (slug.isEmpty()) {
slug = title.toLower().replace(" ", "-")
}
}
}DbValue 类型速查
| 变体 | 对应仓颉类型 | 说明 |
|---|---|---|
DbInt(v: Int64) | Int64 | 整数、时间戳、布尔 0/1 |
DbText(v: String) | String | 文本、JSON、UUID |
DbReal(v: Float64) | Float64 | 浮点数 |
DbBool(v: Bool) | Bool | 布尔(各方言落库格式不同) |
DbBytes(v: Array<UInt8>) | Array<UInt8> | BLOB/BYTEA 二进制 |
DbArray(v: Array<DbValue>) | Array<DbValue> | PG 一维数组(文本协议解析) |
DbNull | None / null | NULL |
OrmException
所有数据库错误统一抛出 OrmException。PostgreSQL 驱动会提取 SQLSTATE 代码:
cangjie
try {
repo.insert(user)
} catch (e: OrmException) {
if (e.code == "23505") {
// 唯一约束冲突 (UNIQUE VIOLATION)
ctx.status(409).json("{\"error\":\"email already exists\"}")
} else if (e.code == "23503") {
// 外键违反 (FOREIGN KEY VIOLATION)
ctx.status(400).json("{\"error\":\"referenced record not found\"}")
} else {
throw e
}
}常见 SQLSTATE:
| 代码 | 含义 |
|---|---|
23505 | 唯一约束冲突 |
23503 | 外键约束违反 |
23502 | NOT NULL 约束违反 |
40001 | 串行化失败(可重试) |
"" | 非 PG 驱动(SQLite/MySQL) |