使用查询构建器进行查询
- [什么是
QueryBuilder
](#什么是QueryBuilder
) - [使用
QueryBuilder
时的重要注意事项](#使用QueryBuilder
时的重要注意事项) - [如何创建和使用
QueryBuilder
](#如何创建和使用QueryBuilder
) - [使用
QueryBuilder
获取值](#使用QueryBuilder
获取值) - 获取计数
- 别名有什么作用?
- 使用参数来转义数据
- [添加
WHERE
表达式](#添加WHERE
表达式) - [添加
HAVING
表达式](#添加HAVING
表达式) - [添加
ORDER BY
表达式](#添加ORDER BY
表达式) - [添加
GROUP BY
表达式](#添加GROUP BY
表达式) - [添加
LIMIT
表达式](#添加LIMIT
表达式) - [添加
OFFSET
表达式](#添加OFFSET
表达式) - 关联查询
- 内连接和左连接
- 不选择任何内容的连接
- 连接任何实体或表
- 连接和映射功能
- 获取生成的查询
- 获取原始结果
- 流式传输结果数据
- 使用分页功能
- 设置锁定
- 使用自定义索引
- 最大执行时间
- 部分选择
- 使用子查询
- 隐藏列
- 查询已删除的行
- 调试
什么是 QueryBuilder
QueryBuilder
是 TypeORM 中最强大的功能之一,它允许您使用优雅和便捷的语法构建 SQL 查询,执行它们并自动转换实体。
下面是一个 QueryBuilder
的简单示例:
const firstUser = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.where("user.id = :id", { id: 1 })
.getOne();
它构建了以下 SQL 查询:
SELECT
user.id as userId,
user.firstName as userFirstName,
user.lastName as userLastName
FROM users user
WHERE user.id = 1
并返回一个 User
的实例:
User {
id: 1,
firstName: "Timber",
lastName: "Saw"
}
使用 QueryBuilder
时的重要注意事项
在使用 QueryBuilder
时,您需要在 WHERE
表达式中提供唯一的参数。以下示例不起作用:
const result = await dataSource
.createQueryBuilder('user')
.leftJoinAndSelect('user.linkedSheep', 'linkedSheep')
.leftJoinAndSelect('user.linkedCow', 'linkedCow')
.where('user.linkedSheep = :id', { id: sheepId })
.andWhere('user.linkedCow = :id', { id: cowId });
...但以下示例可以正常工作:
const result = await dataSource
.createQueryBuilder('user')
.leftJoinAndSelect('user.linkedSheep', 'linkedSheep')
.leftJoinAndSelect('user.linkedCow', 'linkedCow')
.where('user.linkedSheep = :sheepId', { sheepId })
.andWhere('user.linkedCow = :cowId', { cowId });
请注意,我们使用了唯一命名的 :sheepId
和 :cowId
,而不是两次使用 :id
来表示不同的参数。
如何创建和使用 QueryBuilder
有几种方法可以创建 QueryBuilder
:
使用
DataSource
:const user = await dataSource
.createQueryBuilder()
.select("user")
.from(User, "user")
.where("user.id = :id", { id: 1 })
.getOne();使用实体管理器:
const user = await dataSource.manager
.createQueryBuilder(User, "user")
.where("user.id = :id", { id: 1 })
.getOne();使用仓库:
const user = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.where("user.id = :id", { id: 1 })
.getOne();
有 5 种不同类型的查询构建器可用:
SelectQueryBuilder
- 用于构建和执行SELECT
查询。示例:const user = await dataSource
.createQueryBuilder()
.select("user")
.from(User, "user")
.where("user.id = :id", { id: 1 })
.getOne();InsertQueryBuilder
- 用于构建和执行INSERT
查询。示例:await dataSource
.createQueryBuilder()
.insert()
.into(User)
.values([
{ firstName: "Timber", lastName: "Saw" },
{ firstName: "Phantom", lastName: "Lancer" },
])
.execute();UpdateQueryBuilder
- 用于构建和执行UPDATE
查询。示例:await dataSource
.createQueryBuilder()
.update(User)
.set({ firstName: "Timber", lastName: "Saw" })
.where("id = :id", { id: 1 })
.execute();DeleteQueryBuilder
- 用于构建和执行DELETE
查询。示例:await dataSource
.createQueryBuilder()
.delete()
.from(User)
.where("id = :id", { id: 1 })
.execute();RelationQueryBuilder
- 用于构建和执行关联特定操作的查询构建器[TBD]。示例:
await dataSource
.createQueryBuilder()
.relation(User, "photos")
.of(id)
.loadMany();
在任何查询构建器中切换类型时,将会获得一个新的查询构建器实例(与其他所有方法不同)。
使用 QueryBuilder
获取值
要从数据库获取单个结果,例如按 ID 或名称获取用户,必须使用 getOne
:
const timber = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.where("user.id = :id OR user.name = :name", { id: 1, name: "Timber" })
.getOne();
getOneOrFail
从数据库获取单个结果,但如果没有结果,则会抛出 EntityNotFoundError
:
const timber = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.where("user.id = :id OR user.name = :name", { id: 1, name: "Timber" })
.getOneOrFail();
要从数据库获取多个结果,例如获取所有用户,使用 getMany
:
const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.getMany();
使用 select 查询构建器可以获得两种类型的结果:实体或原始结果。
大多数情况下,您需要从数据库选择真实的实体,例如用户。
为此,使用 getOne
和 getMany
。
但有时您需要选择一些特定的数据,比如所有用户照片的总和。
这些数据不是实体,而是称为原始数据。
要获取原始数据,使用 getRawOne
和 getRawMany
。
示例:
const { sum } = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.select("SUM(user.photosCount)", "sum")
.where("user.id = :id", { id: 1 })
.getRawOne();
const photosSums = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.select("user.id")
.addSelect("SUM(user.photosCount)", "sum")
.groupBy("user.id")
.getRawMany();
// 结果将如下所示:[{ id: 1, sum: 25 }, { id: 2, sum: 13 }, ...]
获取计数
您可以使用 getCount()
获取查询返回的行数计数。这将返回一个数字而不是实体结果。
const count = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.where("user.name = :name", { name: "Timber" })
.getCount();
它生成以下 SQL 查询:
SELECT count(*) FROM users user WHERE user.name = 'Timber'
别名的作用是什么?
我们使用了 createQueryBuilder("user")
。但是 "user" 是什么?
它只是一个常规的 SQL 别名。
我们在任何地方都使用别名,除了与选定的数据一起工作时。
createQueryBuilder("user")
等同于:
createQueryBuilder().select("user").from(User, "user");
这将生成以下 SQL 查询:
SELECT ... FROM users user
在这个 SQL 查询中,users
是表名,user
是我们为该表分配的别名。
后续我们使用这个别名来访问该表:
createQueryBuilder()
.select("user")
.from(User, "user")
.where("user.name = :name", { name: "Timber" });
这会生成以下 SQL 查询:
SELECT ... FROM users user WHERE user.name = 'Timber'
如您所见,我们使用了 user
别名来访问用户表。
一个查询构建器不限于一个别名,它们可以具有多个别名。 每个 select 可以有自己的别名, 您可以从多个带有各自别名的表中进行选择, 您可以连接多个具有各自别名的表。 您可以使用这些别名来访问您选择的表(或选择的数据)。
使用参数防止数据转义
我们使用了where("user.name = :name", { name: "Timber" })
。
{ name: "Timber" }
代表什么?它是我们用来防止SQL注入的参数。
我们也可以这样写:where("user.name = '" + name + "')
,
但这样是不安全的,因为它会导致代码遭受SQL注入攻击的风险。
安全的做法是使用特殊的语法:where("user.name = :name", { name: "Timber" })
,
其中:name
是参数名,对应的值在一个对象中指定:{ name: "Timber" }
。
.where("user.name = :name", { name: "Timber" })
是下面代码的简化形式:
.where("user.name = :name")
.setParameter("name", "Timber")
注意:在查询构建器中,不要对不同的值使用相同的参数名。如果多次设置参数值,后面的值会覆盖前面的值。
您还可以提供一个值数组,并使用特殊的展开语法将其转换为SQL语句中的值列表:
.where("user.name IN (:...names)", { names: [ "Timber", "Cristal", "Lina" ] })
这将变成:
WHERE user.name IN ('Timber', 'Cristal', 'Lina')
添加WHERE
条件表达式
添加WHERE
条件表达式非常简单:
createQueryBuilder("user").where("user.name = :name", { name: "Timber" })
它将生成以下SQL查询语句:
SELECT ... FROM users user WHERE user.name = 'Timber'
您可以在现有的WHERE
条件表达式中添加AND
:
createQueryBuilder("user")
.where("user.firstName = :firstName", { firstName: "Timber" })
.andWhere("user.lastName = :lastName", { lastName: "Saw" })
它将生成以下SQL查询语句:
SELECT ... FROM users user WHERE user.firstName = 'Timber' AND user.lastName = 'Saw'
您可以在现有的WHERE
条件表达式中添加OR
:
createQueryBuilder("user")
.where("user.firstName = :firstName", { firstName: "Timber" })
.orWhere("user.lastName = :lastName", { lastName: "Saw" })
它将生成以下SQL查询语句:
SELECT ... FROM users user WHERE user.firstName = 'Timber' OR user.lastName = 'Saw'
您可以使用WHERE
表达式进行IN
查询:
createQueryBuilder("user").where("user.id IN (:...ids)", { ids: [1, 2, 3, 4] })
它将生成以下SQL查询语句:
SELECT ... FROM users user WHERE user.id IN (1, 2, 3, 4)
您可以使用Brackets
将复杂的WHERE
条件表达式添加到现有的WHERE
条件中:
createQueryBuilder("user")
.where("user.registered = :registered", { registered: true })
.andWhere(
new Brackets((qb) => {
qb.where("user.firstName = :firstName", {
firstName: "Timber",
}).orWhere("user.lastName = :lastName", { lastName: "Saw" })
}),
)
它将生成以下SQL查询语句:
SELECT ... FROM users user WHERE user.registered = true AND (user.firstName = 'Timber' OR user.lastName = 'Saw')
您可以使用NotBrackets
在现有的WHERE
条件中添加否定的复杂表达式:
createQueryBuilder("user")
.where("user.registered = :registered", { registered: true })
.andWhere(
new NotBrackets((qb) => {
qb.where("user.firstName = :firstName", {
firstName: "Timber",
}).orWhere("user.lastName = :lastName", { lastName: "Saw" })
}),
)
它将生成以下SQL查询语句:
SELECT ... FROM users user WHERE user.registered = true AND NOT((user.firstName = 'Timber' OR user.lastName = 'Saw'))
您可以根据需要组合任意数量的AND
和OR
表达式。
如果您多次使用.where
,将会覆盖之前的所有WHERE
条件表达式。
注意:对于带有AND
和OR
表达式的复杂表达式,请小心使用orWhere
,它们是堆叠在一起的。
有时候您需要创建一个字符串形式的where
,避免使用orWhere
。
添加 HAVING
表达式
添加 HAVING
表达式很简单:
createQueryBuilder("user").having("user.name = :name", { name: "Timber" })
它将生成以下 SQL 查询语句:
SELECT ... FROM users user HAVING user.name = 'Timber'
您可以在现有的 HAVING
表达式中添加 AND
:
createQueryBuilder("user")
.having("user.firstName = :firstName", { firstName: "Timber" })
.andHaving("user.lastName = :lastName", { lastName: "Saw" })
它将生成以下 SQL 查询语句:
SELECT ... FROM users user HAVING user.firstName = 'Timber' AND user.lastName = 'Saw'
您可以在现有的 HAVING
表达式中添加 OR
:
createQueryBuilder("user")
.having("user.firstName = :firstName", { firstName: "Timber" })
.orHaving("user.lastName = :lastName", { lastName: "Saw" })
它将生成以下 SQL 查询语句:
SELECT ... FROM users user HAVING user.firstName = 'Timber' OR user.lastName = 'Saw'
您可以根据需要组合任意数量的 AND
和 OR
表达式。
如果多次使用 .having
,将覆盖之前的所有 HAVING
表达式。
添加 ORDER BY
表达式
添加 ORDER BY
表达式很简单:
createQueryBuilder("user").orderBy("user.id")
它将生成以下 SQL 查询语句:
SELECT ... FROM users user ORDER BY user.id
您可以更改排序方向,从升序改为降序(或反之):
createQueryBuilder("user").orderBy("user.id", "DESC")
createQueryBuilder("user").orderBy("user.id", "ASC")
您可以添加多个排序标准:
createQueryBuilder("user").orderBy("user.name").addOrderBy("user.id")
您还可以使用排序字段的映射:
createQueryBuilder("user").orderBy({
"user.name": "ASC",
"user.id": "DESC",
})
如果多次使用 .orderBy
,将覆盖之前的所有 ORDER BY
表达式。
添加 DISTINCT ON
表达式(仅适用于 Postgres)
在使用 distinct-on
和 order-by
表达式时,distinct-on
表达式必须与最左边的 order-by
匹配。
distinct-on
表达式使用与 order-by
相同的规则进行解释。请注意,如果没有 order-by
表达式,则使用 distinct-on
意味着每个集合的第一行是不可预测的。
添加 DISTINCT ON
表达式很简单:
createQueryBuilder("user").distinctOn(["user.id"]).orderBy("user.id")
它将生成以下 SQL 查询语句:
SELECT DISTINCT ON (user.id) ... FROM users user ORDER BY user.id
添加 GROUP BY
表达式
添加 GROUP BY
表达式很简单:
createQueryBuilder("user").groupBy("user.id")
它将生成以下 SQL 查询语句:
SELECT ... FROM users user GROUP BY user.id
要添加更多的分组标准,使用 addGroupBy
:
createQueryBuilder("user").groupBy("user.name").addGroupBy("user.id")
如果多次使用 .groupBy
,将覆盖之前的所有 GROUP BY
表达式。
添加 LIMIT
表达式
添加 LIMIT
表达式很简单:
createQueryBuilder("user").limit(10)
它将生成以下 SQL 查询语句:
SELECT ... FROM users user LIMIT 10
生成的 SQL 查询语句取决于数据库的类型(SQL、mySQL、Postgres 等)。
注意:如果您在复杂的带有连接或子查询的查询中使用 LIMIT
,可能不会按照预期工作。
如果您在进行分页操作,建议使用 take
。
添加 OFFSET
表达式
添加 SQL 的 OFFSET
表达式很简单:
createQueryBuilder("user").offset(10)
它将生成以下 SQL 查询语句:
SELECT ... FROM users user OFFSET 10
生成的 SQL 查询语句取决于数据库的类型(SQL、mySQL、Postgres 等)。
注意:如果您在复杂的带有连接或子查询的查询中使用 OFFSET
,可能不会按照预期工作。
如果您在进行分页操作,建议使用 skip
。
关联查询
假设您有以下实体:
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from "typeorm"
import { Photo } from "./Photo"
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
@OneToMany((type) => Photo, (photo) => photo.user)
photos: Photo[]
}
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from "typeorm"
import { User } from "./User"
@Entity()
export class Photo {
@PrimaryGeneratedColumn()
id: number
@Column()
url: string
@ManyToOne((type) => User, (user) => user.photos)
user: User
}
现在假设您想要加载名称为 "Timber" 的用户以及他的所有照片:
const user = await createQueryBuilder("user")
.leftJoinAndSelect("user.photos", "photo")
.where("user.name = :name", { name: "Timber" })
.getOne()
您将获得以下结果:
{
id: 1,
name: "Timber",
photos: [{
id: 1,
url: "me-with-chakram.jpg"
}, {
id: 2,
url: "me-with-trees.jpg"
}]
}
如您所见,leftJoinAndSelect
自动加载了所有 Timber 的照片。
第一个参数是您要加载的关联关系,第二个参数是您为该关联关系的表分配的别名。您可以在查询构建器中的任何位置使用此别名。
例如,让我们获取所有未删除的 Timber 的照片。
const user = await createQueryBuilder("user")
.leftJoinAndSelect("user.photos", "photo")
.where("user.name = :name", { name: "Timber" })
.andWhere("photo.isRemoved = :isRemoved", { isRemoved: false })
.getOne()
这将生成以下 SQL 查询语句:
SELECT user.*, photo.* FROM users user
LEFT JOIN photos photo ON photo.user = user.id
WHERE user.name = 'Timber' AND photo.isRemoved = FALSE
您还可以将条件添加到连接表达式中,而不是使用 "where":
const user = await createQueryBuilder("user")
.leftJoinAndSelect(
"user.photos",
"photo",
"photo.isRemoved = :isRemoved",
{ isRemoved: false },
)
.where("user.name = :name", { name: "Timber" })
.getOne()
这将生成以下 SQL 查询语句:
SELECT user.*, photo.* FROM users user
LEFT JOIN photos photo ON photo.user = user.id AND photo.isRemoved = FALSE
WHERE user.name = 'Timber'
内连接和左连接
如果您想要使用 INNER JOIN
而不是 LEFT JOIN
,只需使用 innerJoinAndSelect
:
const user = await createQueryBuilder("user")
.innerJoinAndSelect(
"user.photos",
"photo",
"photo.isRemoved = :isRemoved",
{ isRemoved: false },
)
.where("user.name = :name", { name: "Timber" })
.getOne()
这将生成:
SELECT user.*, photo.* FROM users user
INNER JOIN photos photo ON photo.user = user.id AND photo.isRemoved = FALSE
WHERE user.name = 'Timber'
LEFT JOIN
和 INNER JOIN
的区别在于,如果用户没有照片,INNER JOIN
不会返回该用户,而 LEFT JOIN
会返回该用户。
要了解更多关于不同连接类型的信息,请参阅 SQL 文档。
无选择的连接
您可以连接数据而无需选择它。要做到这一点,使用 leftJoin
或 innerJoin
:
const user = await createQueryBuilder("user")
.innerJoin("user.photos", "photo")
.where("user.name = :name", { name: "Timber" })
.getOne()
这将生成以下 SQL 查询语句:
SELECT user.* FROM users user
INNER JOIN photos photo ON photo.user = user.id
WHERE user.name = 'Timber'
这将选择 Timber,如果他有照片,但不会返回他的照片。
连接任何实体或表
您不仅可以连接关系,还可以连接其他不相关的实体或表。 示例:
const user = await createQueryBuilder("user")
.leftJoinAndSelect(Photo, "photo", "photo.userId = user.id")
.getMany()
const user = await createQueryBuilder("user")
.leftJoinAndSelect("photos", "photo", "photo.userId = user.id")
.getMany()
连接和映射功能
将 User
实体添加 profilePhoto
属性,您可以使用 QueryBuilder
将任何数据映射到该属性中:
export class User {
// ...
profilePhoto: Photo;
}
const user = await createQueryBuilder("user")
.leftJoinAndMapOne(
"user.profilePhoto",
"user.photos",
"photo",
"photo.isForProfile = TRUE",
)
.where("user.name = :name", { name: "Timber" })
.getOne()
这将加载 Timber 的个人资料照片并将其设置为 user.profilePhoto
。
如果要加载和映射单个实体,请使用 leftJoinAndMapOne
。
如果要加载和映射多个实体,请使用 leftJoinAndMapMany
。
获取生成的查询语句
有时,您可能希望获取 QueryBuilder
生成的 SQL 查询语句。要做到这一点,使用 getSql
:
const sql = createQueryBuilder("user")
.where("user.firstName = :firstName", { firstName: "Timber" })
.orWhere("user.lastName = :lastName", { lastName: "Saw" })
.getSql()
为了调试目的,您可以使用 printSql
:
const users = await createQueryBuilder("user")
.where("user.firstName = :firstName", { firstName: "Timber" })
.orWhere("user.lastName = :lastName", { lastName: "Saw" })
.printSql()
.getMany()
此查询将返回用户并将使用的 SQL 语句打印到控制台。
获取原始结果
使用查询构建器查询时,有两种类型的结果可以获得:实体和原始结果。
大多数情况下,您需要从数据库中选择真实的实体,例如用户。
为此,您可以使用 getOne
和 getMany
。
但是,有时您需要选择特定的数据,比如 所有用户照片的总和。
这样的数据不是实体,而是称为原始数据。要获取原始数据,可以使用 getRawOne
和 getRawMany
。
示例:
const { sum } = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.select("SUM(user.photosCount)", "sum")
.where("user.id = :id", { id: 1 })
.getRawOne()
const photosSums = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.select("user.id")
.addSelect("SUM(user.photosCount)", "sum")
.groupBy("user.id")
.getRawMany()
// 结果将类似于:[{ id: 1, sum: 25 }, { id: 2, sum: 13 }, ...]
流式传输结果数据
您可以使用 stream
方法返回一个流。
流式传输将返回原始数据,您必须手动处理实体转换:
const stream = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.where("user.id = :id", { id: 1 })
.stream()
使用分页
在开发应用程序时,大多数情况下都需要分页功能。 这在您的应用程序中使用分页、页码滑块或无限滚动组件时非常有用。
const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.leftJoinAndSelect("user.photos", "photo")
.take(10)
.getMany()
这将返回前10个用户及其照片。
const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.leftJoinAndSelect("user.photos", "photo")
.skip(10)
.getMany()
这将返回除了前10个用户之外的所有用户及其照片。 您可以结合使用这些方法:
const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.leftJoinAndSelect("user.photos", "photo")
.skip(5)
.take(10)
.getMany()
这将跳过前5个用户并获取之后的10个用户。
take
和 skip
看起来可能类似于使用 limit
和 offset
,但实际上并非如此。
当您使用更复杂的具有连接或子查询的查询时,limit
和 offset
可能无法按预期工作。
使用 take
和 skip
将避免这些问题。
设置锁定
QueryBuilder 支持乐观锁定和悲观锁定。
锁定模式
支持的锁定模式和它们转换为的 SQL 语句如下表所示(空单元格表示不支持)。当指定的锁定模式不受支持时,将抛出 LockNotSupportedOnGivenDriverError
错误。
| | 悲观读取 | 悲观写入 | 脏读 | 悲观部分写入(已弃用,请改用 onLocked) | 悲观写入或失败(已弃用,请改用 onLocked) | 对于无键更新 | 对于键共享 |
| --------------- | ------------------------------------ | ------------------------------------ | ------------ | ---------------------------------------------------------- | ---------------------------------------------------------- | --------------- | ------------ |
| MySQL | FOR SHARE (8+)/LOCK IN SHARE MODE | FOR UPDATE | (无) | FOR UPDATE SKIP LOCKED | FOR UPDATE NOWAIT | | |
| PostgreSQL | FOR SHARE | FOR UPDATE | (无) | FOR UPDATE SKIP LOCKED | FOR UPDATE NOWAIT | FOR NO KEY UPDATE | FOR KEY SHARE |
| Oracle | FOR UPDATE | FOR UPDATE | (无) | | | | |
| SQL Server | WITH (HOLDLOCK, ROWLOCK) | WITH (UPDLOCK, ROWLOCK) | WITH (NOLOCK)| | | | |
| AuroraDataApi | LOCK IN SHARE MODE | FOR UPDATE | (无) | | | | |
| CockroachDB | | FOR UPDATE | (无) | | FOR UPDATE NOWAIT | FOR NO KEY UPDATE | |
要使用悲观读取锁定,请使用以下方法:
const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.setLock("pessimistic_read")
.getMany()
要使用悲观写入锁定,请使用以下方法:
const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.setLock("pessimistic_write")
.getMany()
要使用脏读锁定,请使用以下方法:
const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.setLock("dirty_read")
.getMany()
要使用乐观锁定,请使用以下方法:
const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.setLock("optimistic", existUser.version)
.getMany()
乐观锁定与 @Version
和 @UpdatedDate
装饰器配合使用。
setOnLock
允许您控制在行被锁定时发生的情况。默认情况下,数据库将等待锁定。
您可以通过使用 setOnLocked
控制该行为。
要设置为不等待:
const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.setLock("pessimistic_write")
.setOnLocked("nowait")
.getMany()
要跳过该行:
const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.setLock("pessimistic_write")
.setOnLocked("skip_locked")
.getMany()
基于 锁定模式,数据库对 setOnLocked
的支持如下:
- PostgreSQL:
pessimistic_read
、pessimistic_write
、for_no_key_update
、for_key_share
- MySQL 8+:
pessimistic_read
、pessimistic_write
- MySQL < 8、MariaDB:
pessimistic_write
- CockroachDB:
pessimistic_write
(仅限nowait
)
使用自定义索引
在某些情况下,您可以为数据库服务器提供特定的索引。此功能仅在 MySQL 中支持。
const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.useIndex("my_index") // 索引的名称
.getMany()
最大执行时间
我们可以设置慢查询时间以避免服务器崩溃。
const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.maxExecutionTime(1000) // 毫秒
.getMany()
部分选择
如果您只想选择实体的部分属性,可以使用以下语法:
const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.select(["user.id", "user.name"])
.getMany()
这将只选择 User
实体的 id
和 name
。
使用子查询
您可以轻松创建子查询。子查询在 FROM
、WHERE
和 JOIN
表达式中都受支持。
示例:
const qb = await dataSource.getRepository(Post).createQueryBuilder("post")
const posts = qb
.where(
"post.title IN " +
qb
.subQuery()
.select("user.name")
.from(User, "user")
.where("user.registered = :registered")
.getQuery(),
)
.setParameter("registered", true)
.getMany()
更优雅的方式来完成相同的操作:
const posts = await dataSource
.getRepository(Post)
.createQueryBuilder("post")
.where((qb) => {
const subQuery = qb
.subQuery()
.select("user.name")
.from(User, "user")
.where("user.registered = :registered")
.getQuery()
return "post.title IN " + subQuery
})
.setParameter("registered", true)
.getMany()
或者,您可以创建一个独立的查询构建器并使用其生成的 SQL:
const userQb = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.select("user.name")
.where("user.registered = :registered", { registered: true })
const posts = await dataSource
.getRepository(Post)
.createQueryBuilder("post")
.where("post.title IN (" + userQb.getQuery() + ")")
.setParameters(userQb.getParameters())
.getMany()
您可以在 FROM
中创建子查询,例如:
const userQb = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.select("user.name", "name")
.where("user.registered = :registered", { registered: true })
const posts = await dataSource
.createQueryBuilder()
.select("user.name", "name")
.from("(" + userQb.getQuery() + ")", "user")
.setParameters(userQb.getParameters())
.getRawMany()
或者使用更简洁的语法:
const posts = await dataSource
.createQueryBuilder()
.select("user.name", "name")
.from((subQuery) => {
return subQuery
.select("user.name", "name")
.from(User, "user")
.where("user.registered = :registered", { registered: true })
}, "user")
.getRawMany()
如果您想将子查询作为“第二个 FROM”添加,请使用 addFrom
。
您还可以在 SELECT
语句中使用子查询:
const posts = await dataSource
.createQueryBuilder()
.select("post.id", "id")
.addSelect((subQuery) => {
return subQuery.select("user.name", "name").from(User, "user").limit(1)
}, "name")
.from(Post, "post")
.getRawMany()
隐藏列
如果您查询的模型具有 select: false
的列属性,您必须使用 addSelect
函数才能检索该列的信息。
假设您有以下实体:
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
@Column({ select: false })
password: string
}
使用标准的 find
或查询,您将不会在模型中收到 password
属性。但是,如果您执行以下操作:
const users = await dataSource
.getRepository(User)
.createQueryBuilder()
.select("user.id", "id")
.addSelect("user.password")
.getMany()
您将在查询中获取 password
属性。
查询已删除的行
如果您查询的模型具有设置了 @DeleteDateColumn
属性的列,查询构建器将自动查询“软删除”的行。
假设您有以下实体:
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
@DeleteDateColumn()
deletedAt?: Date
}
使用标准的 find
或查询,您将不会收到具有该行值的行。但是,如果您执行以下操作:
const users = await dataSource
.getRepository(User)
.createQueryBuilder()
.select("user.id", "id")
.withDeleted()
.getMany()
您将获得所有行,包括已删除的行。
公共表达式(Common Table Expressions)
如果您的数据库的最低支持版本支持公共表达式,QueryBuilder
实例支持 公共表达式。目前,Oracle 还不支持公共表达式。
const users = await connection.getRepository(User)
.createQueryBuilder('user')
.select("user.id", 'id')
.addCommonTableExpression(`
SELECT "userId" FROM "post"
`, 'post_users_ids')
.where(`user.id IN (SELECT "userId" FROM 'post_users_ids')`)
.getMany();
InsertQueryBuilder
或 UpdateQueryBuilder
的结果值可以在 Postgres 中使用:
const insertQueryBuilder = connection.getRepository(User)
.createQueryBuilder()
.insert({
name: 'John Smith'
})
.returning(['id']);
const users = await connection.getRepository(User)
.createQueryBuilder('user')
.addCommonTableExpression(insertQueryBuilder, 'insert_results')
.where(`user.id IN (SELECT "id" FROM 'insert_results')`)
.getMany();
时光旅行查询
时光旅行查询
目前仅支持 CockroachDB
数据库。
const repository = connection.getRepository(Account)
// 创建一个新的账户
const account = new Account()
account.name = "John Smith"
account.balance = 100
await repository.save(account)
// 假设我们在创建后的1小时内更新账户余额
account.balance = 200
await repository.save(account)
// 输出结果为 { name: "John Smith", balance: "200" }
console.log(account)
// 加载1小时前的账户状态
account = await repository
.createQueryBuilder("account")
.timeTravelQuery(`'-1h'`)
.getOneOrFail()
// 输出结果为 { name: "John Smith", balance: "100" }
console.log(account)
默认情况下,timeTravelQuery()
使用 follower_read_timestamp()
函数,如果没有传递参数。
有关另支持的时间戳参数和其他信息,请参考
CockroachDB 文档。
调试
您可以通过调用 getQuery()
或 getQueryAndParameters()
从查询构建器中获取生成的 SQL。
如果只想获取查询语句,可以使用 getQuery()
const sql = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.where("user.id = :id", { id: 1 })
.getQuery()
结果为:
SELECT `user`.`id` as `userId`, `user`.`firstName` as `userFirstName`, `user`.`lastName` as `userLastName` FROM `users` `user` WHERE `user`.`id` = ?
或者如果您想要查询和参数,可以使用 getQueryAndParameters()
获取一个数组返回
const queryAndParams = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.where("user.id = :id", { id: 1 })
.getQueryAndParameters()
结果为:
[
"SELECT `user`.`id` as `userId`, `user`.`firstName` as `userFirstName`, `user`.`lastName` as `userLastName` FROM `users` `user` WHERE `user`.`id` = ?",
[ 1 ]
]