Skip to content

实体 @Entity

ACE ORM 采用编译期宏路线:@Entity 在构建阶段生成 MapperRepository,运行时零反射开销

支持的数据库

数据库driver说明
SQLitesqlite嵌入式,无需服务
PostgreSQLpostgres推荐生产使用
MySQLmysql兼容 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,PG BIGSERIAL,MySQL BIGINT 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 = ""
选项类型默认说明
nullableBooltruefalse → DDL 追加 NOT NULL
uniqueBoolfalse生成 UNIQUE 约束
defaultSqlString""DDL DEFAULT 值(原始 SQL,如 "CURRENT_TIMESTAMP"
lengthInt640字符串列长度,0 = 不限
typeString""强制覆盖方言类型(如 "JSONB", "UUID"
autoIncrementBoolfalse同 @Id,通常不单独使用

仓颉类型 → 列类型对照

仓颉类型SQLitePostgreSQLMySQL
Int64INTEGERBIGINTBIGINT
StringTEXTTEXTLONGTEXT
Float64REALDOUBLE PRECISIONDOUBLE
BoolINTEGERBOOLEANTINYINT(1)
Array<UInt8>BLOBBYTEALONGBLOB

@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) 重置为 NULL
  • repo.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 一维数组(文本协议解析)
DbNullNone / nullNULL

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外键约束违反
23502NOT NULL 约束违反
40001串行化失败(可重试)
""非 PG 驱动(SQLite/MySQL)

基于 Apache-2.0 许可证发布