高级特性
ValueTransformer — 列值转换器
ValueTransformer 在读/写数据库时对字段值做双向转换:可用于加密存储、枚举序列化、JSON 字段等。
定义转换器
package demo.transform
import ace_orm.*
// 枚举 ↔ 字符串转换器
public class StatusTransformer <: ValueTransformer {
// 写入 DB 前:仓颉值 → DbValue
public func to(v: DbValue): DbValue {
match (v) {
case DbText("active") => DbText("A")
case DbText("inactive") => DbText("I")
case DbText("banned") => DbText("B")
case _ => DbNull
}
}
// 从 DB 读出后:DbValue → 仓颉值
public func from(v: DbValue): DbValue {
match (v) {
case DbText("A") => DbText("active")
case DbText("I") => DbText("inactive")
case DbText("B") => DbText("banned")
case _ => DbText("unknown")
}
}
}在 @Column 上绑定
@Entity["users"]
public class User {
@Id[]
public var id: Int64 = 0
// transformer = 转换器类名(字符串),type = 存储类型(覆盖自动推断)
@Column[transformer: "StatusTransformer", type: "TEXT"]
public var status: String = "active"
}TIP
转换器在宏编译时注册到全局表,运行时零反射直调。type= 必须显式指定为存储形态的 DDL 类型,否则方言自动推断可能不符预期。
加密转换器示例
public class AesTransformer <: ValueTransformer {
public func to(v: DbValue): DbValue {
match (v) {
case DbText(s) => DbText(aesEncrypt(s))
case _ => v
}
}
public func from(v: DbValue): DbValue {
match (v) {
case DbText(s) => DbText(aesDecrypt(s))
case _ => v
}
}
}@Embeddable / @Embedded — 嵌入实体
将多个字段封装为值对象(@Embeddable 类),嵌入到宿主实体(@Embedded),数据库仍展平到宿主表。
定义嵌入类
@Embeddable
public class Address {
public var street: String = ""
public var city: String = ""
public var country: String = ""
public var zipCode: String = ""
}嵌入到宿主实体
@Entity["users"]
public class User {
@Id[]
public var id: Int64 = 0
@Column[]
public var name: String = ""
// 展平到 users 表,列名 = street/city/country/zipCode
@Embedded[]
public var address: Address = Address()
// 带前缀:列名 = shipping_street / shipping_city / ...
@Embedded[prefix: "shipping_"]
public var shippingAddress: Address = Address()
}实际建表:
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
street TEXT,
city TEXT,
country TEXT,
zipCode TEXT,
shipping_street TEXT,
shipping_city TEXT,
shipping_country TEXT,
shipping_zipCode TEXT
);使用
let user = User()
user.name = "Alice"
user.address.street = "中关村大街 1 号"
user.address.city = "北京"
user.address.country = "CN"
userRepo.insert(user)
let loaded = userRepo.findById(DbInt(1)).getOrThrow()
println(loaded.address.city) // "北京"限制
- 仅支持单层嵌入(嵌入类内不能再
@Embedded) - 宿主字段类型必须为
@Embeddable类本身,不支持?Address - 嵌入类内部
@Column["alias"]显式别名暂不生效,列名仅由字段名(+ 前缀)决定
单表继承 STI
STI(Single Table Inheritance)将类继承层次的所有子类存到同一张表,通过判别列区分类型。
基类
@Entity["vehicles"]
@TableInheritance["type"] // "type" = 判别列名
open public class Vehicle {
@Id[]
public var id: Int64 = 0
@Column[]
public var make: String = ""
@Column[]
public var model: String = ""
}子类
@ChildEntity[Vehicle, "car"] // "car" = 判别值(写入 type 列)
public class Car <: Vehicle {
@Column[]
public var doors: Int64 = 4
}
@ChildEntity[Vehicle, "truck"]
public class Truck <: Vehicle {
@Column[]
public var payload: Float64 = 0.0
}使用
// 子仓储自动按判别值过滤
let cars = carRepo.findAll() // SELECT ... WHERE type = 'car'
// 子类独有字段可正常赋值
let car = Car()
car.make = "Toyota"
car.model = "Camry"
car.doors = 4
carRepo.insert(car)
// 基类仓储多态返回(根据 type 列构造对应子类实例)
let all = vehicleRepo.findAll() // Array<Vehicle>,实例可能是 Car 或 Truck
for (v in all) {
match (v) {
case c: Car => println("Car: ${c.make} ${c.doors} doors")
case t: Truck => println("Truck: ${t.make} payload ${t.payload}t")
case _ => ()
}
}STI 限制
- 子类自有关系(
@ManyToOne等)暂不支持 JOIN 展开,使用基类映射器操作 - 子类之间共享同一张表,子类独有列对其他子类的行为 NULL(ORM 自动处理)
@EventSubscriber — 全局事件订阅
@EventSubscriber 跨实体监听生命周期事件(insert/update/remove),无需在每个实体上重复声明。
定义订阅者
package demo.audit
import ace_orm.*
import ace_orm.macros.*
@EventSubscriber
public class AuditSubscriber {
// 任意实体 insert 成功后触发
@AfterInsert[]
func onInsert(table: String, id: Int64): Unit {
println("[audit] INSERT ${table} id=${id}")
}
// 任意实体 update 成功后触发
@AfterUpdate[]
func onUpdate(table: String, id: Int64): Unit {
println("[audit] UPDATE ${table} id=${id}")
}
// 任意实体 remove 成功后触发
@AfterRemove[]
func onRemove(table: String, id: Int64): Unit {
println("[audit] DELETE ${table} id=${id}")
}
}订阅者在程序启动期自动注册(与 @Service/@Controller 相同机制),无需手动 register。
用途示例
- 写审计日志(操作人、时间、受影响表/行)
- 发布领域事件到消息队列
- 清空缓存(实体更新后让 TtlCache 失效)
InstrumentedDriver — 可观测性装饰器
InstrumentedDriver 以装饰器模式包裹任意 Driver,在每次 SQL 执行前后注入观测逻辑:慢查询日志、Metrics 计数、追踪等。
基本用法
import ace_orm.*
// 包裹真实驱动
let realDriver = SqliteDriver("app.db")
let driver = InstrumentedDriver(realDriver)
let ds = DataSource(driver)默认行为:
- 执行耗时 > 100ms 的 SQL 写入结构化慢查询日志
- 每次
exec/query记录执行计数
自定义阈值
// 将慢查询阈值改为 50ms
let driver = InstrumentedDriver(realDriver, slowMs: 50)与 OrmComponent 集成
在 ace.toml 中开启可观测:
[datasource]
driver = "postgres"
url = "host=localhost port=5432 user=app password=secret dbname=appdb"
instrumented = true # 自动包裹 InstrumentedDriver
slowMs = 100启用后,OrmComponent 自动用 InstrumentedDriver 包裹底层驱动,并将指标暴露给 ace-observability(若引入)。
重要:streamQuery 委托
InstrumentedDriver 对 streamQuery 做了显式委托:
// InstrumentedDriver 内部
public func streamQuery(sql, params, chunkSize, cb) {
inner.streamQuery(sql, params, chunkSize, cb) // 委托给真实驱动
}这确保 PostgreSQL 的原生游标(DECLARE/FETCH/CLOSE)不被 LIMIT/OFFSET 降级替换。
连接池配置
ConnectionPool 由各驱动内部管理,通过 URL 参数配置:
# PostgreSQL
host=localhost port=5432 user=app password=secret dbname=db pool.max=20 pool.min=2
# MySQL
host=127.0.0.1 port=3306 user=root password=xxx dbname=app pool.max=10 pool.min=1| 参数 | 说明 | 默认 |
|---|---|---|
pool.max | 最大连接数 | 10 |
pool.min | 最小保持连接数(空闲回收保底) | 1 |
SQLite 每操作开关连接(perOp 模式),无连接池概念。
健康检查
import ace_orm.*
let health = OrmHealthIndicator(ds)
match (health.check()) {
case HealthStatus.UP => println("DB OK")
case HealthStatus.DOWN => println("DB 不可用")
}与 ace-observability 的 /actuator/health 端点自动集成。
多数据源
import ace_orm.*
let primary = DataSource("postgres",
"host=primary-db port=5432 user=app password=secret dbname=app")
let replica = DataSource("postgres",
"host=replica-db port=5432 user=app password=secret dbname=app pool.max=30")
// 注册命名数据源
DataSourceRegistry.register("primary", primary)
DataSourceRegistry.register("replica", replica)
// 在代码中按名获取
let ds = DataSourceRegistry.get("replica").getOrThrow()
let repo = UserRepository(ds)
let users = repo.findAll()读写分离
@Service
public class UserService {
var writeRepo: UserRepository
var readRepo: UserRepository
public init() {
writeRepo = UserRepository(DataSourceRegistry.get("primary").getOrThrow())
readRepo = UserRepository(DataSourceRegistry.get("replica").getOrThrow())
}
@Transactional
public func createUser(req: CreateUserReq): User {
writeRepo.insert(User(name: req.name, email: req.email))
}
public func listUsers(): Array<User> {
readRepo.findAll() // 读副本
}
}路由驱动(RoutingDriver)
RoutingDriver 是多数据源路由的另一种方式:单个驱动对象,根据规则动态选择后端:
import ace_orm.*
let routing = RoutingDriver(HashMap<String, Driver>([
("write", PostgresDriver("host=primary-db ...")),
("read", PostgresDriver("host=replica-db ..."))
]), defaultKey: "write")
// 在事务中强制走写库
let ds = DataSource(routing)查询缓存
通过 InstrumentedDriver 或 Repository 级配置开启查询结果缓存(基于 TtlCache):
// Repository 方法级缓存(@Cacheable 需引入 ace-observability)
@Cacheable[ttl: 300] // 缓存 300 秒
public func hotQuery(): Array<User> {
userRepo.findBy([eq("status", DbText("active"))])
}详见 可观测性文档。