QueryBuilder
当 Repository 的便利方法不能满足复杂查询(多 JOIN、GROUP BY、子查询、动态 OR、原始片段)时,使用 QueryBuilder 以链式方式构建类型安全的 SQL。
SelectQueryBuilder
获取构造器
cangjie
// 从 Repository 入口(推荐)——泛型类型自动绑定
let qb = postRepo.createQueryBuilder("p") // 别名 "p" → SELECT p.* FROM posts p
// 从 DataSource 手动创建
let qb = SelectQueryBuilder<Post>(ds, PostMapper(), "p")基础用例:过滤 + 排序 + 分页
cangjie
let page = 1 // 0-based
let perPage = 20
let posts = postRepo
.createQueryBuilder("p")
.andWhere("p.status", "=", DbText("published"))
.andWhere("p.categoryId", "=", DbInt(catId))
.orderBy("p.createdAt", "DESC")
.limit(perPage)
.offset(page * perPage)
.getMany()getMany / getOne / getCount
cangjie
let list = qb.getMany() // Array<T>
let first = qb.getOne() // ?T — 附加 LIMIT 1
let count = qb.getCount() // Int64 — SELECT COUNT(*)
let (rows, total) = qb.getManyAndCount() // 查询 + 计数(各执行一条 SQL)WHERE 条件
cangjie
// 简单等值/比较(AND 连接)
qb.andWhere("p.authorId", "=", DbInt(userId))
qb.andWhere("p.score", ">", DbReal(4.5))
// 原始片段(支持多参数、复杂表达式)
qb.andWhereRaw("p.createdAt BETWEEN ? AND ?", [DbInt(from), DbInt(to)])
// OR 条件(配合 Cond 构建器)
qb.orWhere(eq("p.status", DbText("featured")))
qb.orWhere(orGroup([
eq("p.authorId", DbInt(1)),
eq("p.authorId", DbInt(2))
]))JOIN(仅过滤,不装填对象)
cangjie
// LEFT JOIN(不影响主表行数)
qb.leftJoin("users", "u", "u.id = p.authorId")
.andWhere("u.role", "=", DbText("admin"))
// INNER JOIN(过滤主表)
qb.innerJoin("categories", "c", "c.id = p.categoryId")
.andWhere("c.active", "=", DbBool(true))leftJoinAndSelect / innerJoinAndSelect(装填对象图)
cangjie
// 联表并将 author 字段填充为 User 实体
let posts = postRepo
.createQueryBuilder("p")
.leftJoinAndSelect("author", "u") // "author" = Post 上的 @ManyToOne 字段名
.innerJoinAndSelect("category", "c")
.andWhere("p.published", "=", DbBool(true))
.getMany()
// posts[0].author → Some(User{...})
// posts[0].category → Some(Category{...})INFO
leftJoinAndSelect 只支持 to-one 关系(@ManyToOne / @OneToOne)。集合关系使用 loadCollection 懒加载。
命名参数 setParameter
复杂查询需要多处引用同一值时,使用命名参数(对标 TypeORM setParameter):
cangjie
let posts = postRepo
.createQueryBuilder("p")
.andWhereRaw("p.publishedAt > :cutoff AND p.expiresAt > :cutoff", [])
.andWhereRaw("p.authorId = :uid", [])
.setParameter("cutoff", DbInt(nowMs))
.setParameter("uid", DbInt(userId))
.getMany()批量设置:
cangjie
qb.setParameters(HashMap<String, DbValue>([
("status", DbText("active")),
("minScore", DbReal(3.0))
]))子查询 whereInSubquery
cangjie
// 找 tag 名包含 "cangjie" 的所有 post
let tagSub = tagRepo
.createQueryBuilder("t")
.select("t.id")
.andWhere("t.name", "LIKE", DbText("%cangjie%"))
let posts = postRepo
.createQueryBuilder("p")
.whereInSubquery("p.id", tagSub) // p.id IN (SELECT t.id FROM tags t WHERE ...)
.getMany()自定义 SELECT(getRawMany)
cangjie
// 联表统计(不映射回实体)
let stats = postRepo
.createQueryBuilder("p")
.select("p.categoryId, COUNT(*) as cnt, AVG(p.score) as avgScore")
.leftJoin("categories", "c", "c.id = p.categoryId")
.groupBy("p.categoryId")
.having("COUNT(*) > ?", [DbInt(5)])
.orderBy("cnt", "DESC")
.getRawMany()
// 访问原始列
for (row in stats) {
println("${row.getText("categoryId")}: ${row.getText("cnt")}")
}GROUP BY / HAVING
cangjie
qb.groupBy("p.authorId")
.having("COUNT(*) >= ?", [DbInt(10)])DISTINCT
cangjie
let uniqueAuthors = postRepo
.createQueryBuilder("p")
.distinct()
.select("p.authorId")
.getRawMany()悲观锁
cangjie
// 写锁(SELECT ... FOR UPDATE)
let task = taskRepo
.createQueryBuilder("t")
.andWhere("t.id", "=", DbInt(taskId))
.forUpdate()
.getOne()
// 读锁(SELECT ... FOR SHARE)
let task = taskRepo.createQueryBuilder("t")
.andWhere("t.id", "=", DbInt(taskId))
.forShare()
.getOne()含软删除行(withDeleted)
cangjie
let allRows = postRepo
.createQueryBuilder("p")
.withDeleted()
.getMany()流式读取(stream)
cangjie
postRepo
.createQueryBuilder("p")
.andWhere("p.status", "=", DbText("published"))
.orderBy("p.id", "ASC")
.stream(500, {batch =>
for (p in batch) { exportToEs(p) }
})内省 SQL(toSql)
cangjie
let sql = qb.toSql() // 返回最终渲染后的 SQL 字符串(占位已按方言渲染,值未绑定)
println(sql)InsertQueryBuilder
cangjie
// 单行
let id = repo.createInsertQueryBuilder()
.columns(["title", "authorId", "createdAt"])
.values([DbText("Hello"), DbInt(userId), DbInt(nowMs)])
.execute()
// 批量(一条 SQL 多 VALUES 子句)
let ib = repo.createInsertQueryBuilder()
.columns(["name", "score"])
for (row in data) {
ib.values([DbText(row.name), DbReal(row.score)])
}
let lastId = ib.execute()直接使用 DataSource
当你没有 Repository 时,也可以通过 DataSource 直接创建:
cangjie
InsertQueryBuilder(ds, "audit_logs")
.columns(["action", "userId", "ts"])
.values([DbText("login"), DbInt(uid), DbInt(nowMs)])
.execute()UpdateQueryBuilder
cangjie
// 按条件批量更新
repo.createUpdateQueryBuilder()
.set("status", DbText("expired"))
.set("updatedAt", DbInt(nowMs))
.andWhere(lt("expiresAt", DbInt(nowMs)))
.andWhere(eq("status", DbText("active")))
.execute()
// PostgreSQL RETURNING(获取更新后的行)
let updated = repo.createUpdateQueryBuilder()
.set("score", DbReal(99.9))
.andWhere(eq("id", DbInt(userId)))
.returning(["id", "score", "updatedAt"])
.executeReturning()
for (row in updated) {
println("updated id=${row.getInt("id")} score=${row.getReal("score")}")
}DeleteQueryBuilder
cangjie
// 按条件删除
repo.createDeleteQueryBuilder()
.andWhere(lt("createdAt", DbInt(cutoffMs)))
.andWhere(eq("status", DbText("archived")))
.execute()
// PostgreSQL RETURNING(获取被删除的行)
let deleted = repo.createDeleteQueryBuilder()
.andWhere(eq("id", DbInt(targetId)))
.returning(["id", "title"])
.executeReturning()RETURNING 方言限制
returning() + executeReturning() 仅 PostgreSQL 支持。在 SQLite/MySQL 上调用 executeReturning() 会抛 OrmException。
完整复合示例:分页 + 联表 + 统计
cangjie
package demo.service
import ace_framework.*
import ace_orm.*
@Service
public class ArticleService {
@Inject
var articleRepo: ArticleRepository
func listPublished(page: Int64, pageSize: Int64): (Array<Article>, Int64) {
let base = articleRepo
.createQueryBuilder("a")
.leftJoinAndSelect("author", "u")
.andWhere("a.status", "=", DbText("published"))
.orderBy("a.createdAt", "DESC")
let total = base.getCount()
let items = base
.skip(page * pageSize)
.take(pageSize)
.getMany()
return (items, total)
}
func topAuthors(minPosts: Int64): Array<Row> {
articleRepo
.createQueryBuilder("a")
.select("a.authorId, COUNT(*) as posts")
.andWhere("a.status", "=", DbText("published"))
.groupBy("a.authorId")
.having("COUNT(*) >= ?", [DbInt(minPosts)])
.orderBy("posts", "DESC")
.limit(10)
.getRawMany()
}
}API 速查表
SelectQueryBuilder
| 方法 | 说明 |
|---|---|
andWhere(col, op, val) | 等值/比较条件(AND) |
andWhereRaw(frag, params) | 原始 WHERE 片段(AND) |
orWhere(cond) | OR 顶层条件 |
whereInSubquery(col, sub) | col IN (子查询) |
leftJoin(table, alias, on) | LEFT JOIN(不装填对象) |
innerJoin(table, alias, on) | INNER JOIN(不装填对象) |
leftJoinAndSelect(field, alias) | LEFT JOIN + 装填对象图 |
innerJoinAndSelect(field, alias) | INNER JOIN + 装填对象图 |
select(cols) | 自定义 SELECT 子句 |
orderBy(col, dir) | 排序(可多次调用) |
groupBy(col) | GROUP BY |
having(frag, params) | HAVING |
distinct() | DISTINCT |
limit(n) / take(n) | 最多取 n 行 |
offset(n) / skip(n) | 跳过前 n 行 |
forUpdate() | 悲观写锁 |
forShare() | 悲观读锁 |
withDeleted() | 包含软删除行 |
setParameter(name, val) | 命名参数 |
setParameters(map) | 批量命名参数 |
getMany() | 返回 Array<T> |
getOne() | 返回 ?T(LIMIT 1) |
getCount() | 返回 Int64 |
getManyAndCount() | 返回 (Array<T>, Int64) |
getRawMany() | 返回 Array<Row> |
stream(chunk, cb) | 流式批量读 |
toSql() | 返回渲染后 SQL |
InsertQueryBuilder
| 方法 | 说明 |
|---|---|
columns(names) | 指定列名 |
values(row) | 追加一行值 |
valuesMany(rows) | 批量追加行 |
execute() | 执行,返回 Int64 last-insert-id(PG 无此概念恒 0,请用 RETURNING) |
toSql() | 返回渲染后 SQL |
UpdateQueryBuilder
| 方法 | 说明 |
|---|---|
set(col, val) | 设置一列新值 |
andWhere(cond) | WHERE 条件 |
returning(cols) | RETURNING 列(仅 PG) |
execute() | 执行,返回受影响行数 |
executeReturning() | 执行,返回 Array<Row>(仅 PG) |
toSql() | 返回渲染后 SQL |
DeleteQueryBuilder
| 方法 | 说明 |
|---|---|
andWhere(cond) | WHERE 条件 |
returning(cols) | RETURNING 列(仅 PG) |
execute() | 执行,返回受影响行数 |
executeReturning() | 执行,返回 Array<Row>(仅 PG) |
toSql() | 返回渲染后 SQL |