Skip to content

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

基于 Apache-2.0 许可证发布